user-group-relation.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import { isPopulated, type IUserGroupHasId, type IUserGroupRelation } from '@growi/core';
  2. import mongoose, { Model, Schema, Document } from 'mongoose';
  3. import { ObjectIdLike } from '../interfaces/mongoose-utils';
  4. import { getOrCreateModel } from '../util/mongoose-utils';
  5. import { UserGroupDocument } from './user-group';
  6. const debug = require('debug')('growi:models:userGroupRelation');
  7. const mongoosePaginate = require('mongoose-paginate-v2');
  8. const uniqueValidator = require('mongoose-unique-validator');
  9. const ObjectId = Schema.Types.ObjectId;
  10. export interface UserGroupRelationDocument extends IUserGroupRelation, Document {}
  11. export interface UserGroupRelationModel extends Model<UserGroupRelationDocument> {
  12. [x:string]: any, // for old methods
  13. PAGE_ITEMS: 50,
  14. removeAllByUserGroups: (groupsToDelete: UserGroupDocument[]) => Promise<any>,
  15. findAllUserIdsForUserGroups: (userGroupIds: ObjectIdLike[]) => Promise<string[]>,
  16. findGroupsWithDescendantsByGroupAndUser: (group: UserGroupDocument, user) => Promise<UserGroupDocument[]>,
  17. countByGroupIdsAndUser: (userGroupIds: ObjectIdLike[], userData) => Promise<number>
  18. findAllGroupsForUser: (user) => Promise<UserGroupDocument[]>
  19. }
  20. /*
  21. * define schema
  22. */
  23. const schema = new Schema<UserGroupRelationDocument, UserGroupRelationModel>({
  24. relatedGroup: { type: ObjectId, ref: 'UserGroup', required: true },
  25. relatedUser: { type: ObjectId, ref: 'User', required: true },
  26. }, {
  27. timestamps: { createdAt: true, updatedAt: false },
  28. });
  29. schema.plugin(mongoosePaginate);
  30. schema.plugin(uniqueValidator);
  31. /**
  32. * remove all invalid relations that has reference to unlinked document
  33. */
  34. schema.statics.removeAllInvalidRelations = function() {
  35. return this.findAllRelation()
  36. .then((relations) => {
  37. // filter invalid documents
  38. return relations.filter((relation) => {
  39. return relation.relatedUser == null || relation.relatedGroup == null;
  40. });
  41. })
  42. .then((invalidRelations) => {
  43. const ids = invalidRelations.map((relation) => { return relation._id });
  44. return this.deleteMany({ _id: { $in: ids } });
  45. });
  46. };
  47. /**
  48. * find all user and group relation
  49. *
  50. * @static
  51. * @returns {Promise<UserGroupRelation[]>}
  52. * @memberof UserGroupRelation
  53. */
  54. schema.statics.findAllRelation = function() {
  55. return this
  56. .find()
  57. .populate('relatedUser')
  58. .populate('relatedGroup')
  59. .exec();
  60. };
  61. /**
  62. * find all user and group relation of UserGroup
  63. *
  64. * @static
  65. * @param {UserGroup} userGroup
  66. * @returns {Promise<UserGroupRelation[]>}
  67. * @memberof UserGroupRelation
  68. */
  69. schema.statics.findAllRelationForUserGroup = function(userGroup) {
  70. debug('findAllRelationForUserGroup is called', userGroup);
  71. return this
  72. .find({ relatedGroup: userGroup })
  73. .populate('relatedUser')
  74. .exec();
  75. };
  76. schema.statics.findAllUserIdsForUserGroups = async function(userGroupIds: ObjectIdLike[]): Promise<string[]> {
  77. const relations = await this
  78. .find({ relatedGroup: { $in: userGroupIds } })
  79. .select('relatedUser')
  80. .exec();
  81. // return unique ids
  82. return [...new Set(relations.map(r => r.relatedUser.toString()))];
  83. };
  84. /**
  85. * find all user and group relation of UserGroups
  86. *
  87. * @static
  88. * @param {UserGroup[]} userGroups
  89. * @returns {Promise<UserGroupRelation[]>}
  90. * @memberof UserGroupRelation
  91. */
  92. schema.statics.findAllRelationForUserGroups = function(userGroups) {
  93. return this
  94. .find({ relatedGroup: { $in: userGroups } })
  95. .populate('relatedUser')
  96. .exec();
  97. };
  98. /**
  99. * find all groups of User
  100. *
  101. * @static
  102. * @param {User} user
  103. * @returns {Promise<UserGroupDocument[]>}
  104. * @memberof UserGroupRelation
  105. */
  106. schema.statics.findAllGroupsForUser = async function(user): Promise<UserGroupDocument[]> {
  107. const userGroupRelations = await this.find({ relatedUser: user.id }).populate('relatedGroup');
  108. const userGroups = userGroupRelations.map((relation) => {
  109. return isPopulated(relation.relatedGroup) ? relation.relatedGroup as UserGroupDocument : null;
  110. });
  111. return userGroups.filter((group): group is NonNullable<UserGroupDocument> => group != null);
  112. };
  113. /**
  114. * find all UserGroup IDs that related to specified User
  115. *
  116. * @static
  117. * @param {User} user
  118. * @returns {Promise<ObjectId[]>}
  119. */
  120. schema.statics.findAllUserGroupIdsRelatedToUser = async function(user) {
  121. const relations = await this.find({ relatedUser: user._id })
  122. .select('relatedGroup')
  123. .exec();
  124. return relations.map((relation) => { return relation.relatedGroup });
  125. };
  126. /**
  127. * count by related group id and related user
  128. *
  129. * @static
  130. * @param {string} userGroupId find query param for relatedGroup
  131. * @param {User} userData find query param for relatedUser
  132. * @returns {Promise<number>}
  133. */
  134. schema.statics.countByGroupIdsAndUser = async function(userGroupIds: ObjectIdLike[], userData): Promise<number> {
  135. const query = {
  136. relatedGroup: { $in: userGroupIds },
  137. relatedUser: userData.id,
  138. };
  139. return this.count(query);
  140. };
  141. /**
  142. * find all "not" related user for UserGroup
  143. *
  144. * @static
  145. * @param {UserGroup} userGroup for find users not related
  146. * @returns {Promise<User>}
  147. * @memberof UserGroupRelation
  148. */
  149. schema.statics.findUserByNotRelatedGroup = function(userGroup, queryOptions) {
  150. const User = mongoose.model('User') as any;
  151. let searchWord = new RegExp(`${queryOptions.searchWord}`);
  152. switch (queryOptions.searchType) {
  153. case 'forward':
  154. searchWord = new RegExp(`^${queryOptions.searchWord}`);
  155. break;
  156. case 'backword':
  157. searchWord = new RegExp(`${queryOptions.searchWord}$`);
  158. break;
  159. }
  160. const searthField: Record<string, RegExp>[] = [
  161. { username: searchWord },
  162. ];
  163. if (queryOptions.isAlsoMailSearched === 'true') { searthField.push({ email: searchWord }) }
  164. if (queryOptions.isAlsoNameSearched === 'true') { searthField.push({ name: searchWord }) }
  165. return this.findAllRelationForUserGroup(userGroup)
  166. .then((relations) => {
  167. const relatedUserIds = relations.map((relation) => {
  168. return relation.relatedUser.id;
  169. });
  170. const query = {
  171. _id: { $nin: relatedUserIds },
  172. status: User.STATUS_ACTIVE,
  173. $or: searthField,
  174. };
  175. debug('findUserByNotRelatedGroup ', query);
  176. return User.find(query).exec();
  177. });
  178. };
  179. /**
  180. * get if the user has relation for group
  181. *
  182. * @static
  183. * @param {UserGroup} userGroup
  184. * @param {User} user
  185. * @returns {Promise<boolean>} is user related for group(or not)
  186. * @memberof UserGroupRelation
  187. */
  188. schema.statics.isRelatedUserForGroup = function(userGroup, user) {
  189. const query = {
  190. relatedGroup: userGroup.id,
  191. relatedUser: user.id,
  192. };
  193. return this
  194. .count(query)
  195. .exec()
  196. .then((count) => {
  197. // return true or false of the relation is exists(not count)
  198. return (count > 0);
  199. });
  200. };
  201. /**
  202. * create user and group relation
  203. *
  204. * @static
  205. * @param {UserGroup} userGroup
  206. * @param {User} user
  207. * @returns {Promise<UserGroupRelation>} created relation
  208. * @memberof UserGroupRelation
  209. */
  210. schema.statics.createRelation = function(userGroup, user) {
  211. return this.create({
  212. relatedGroup: userGroup.id,
  213. relatedUser: user.id,
  214. });
  215. };
  216. schema.statics.createRelations = async function(userGroupIds, user) {
  217. const documentsToInsertMany = userGroupIds.map((groupId) => {
  218. return {
  219. relatedGroup: groupId,
  220. relatedUser: user._id,
  221. createdAt: new Date(),
  222. };
  223. });
  224. return this.insertMany(documentsToInsertMany);
  225. };
  226. /**
  227. * remove all relation for UserGroup
  228. *
  229. * @static
  230. * @param {UserGroup} userGroup related group for remove
  231. * @returns {Promise<any>}
  232. * @memberof UserGroupRelation
  233. */
  234. schema.statics.removeAllByUserGroups = function(groupsToDelete: UserGroupDocument[]) {
  235. return this.deleteMany({ relatedGroup: { $in: groupsToDelete } });
  236. };
  237. /**
  238. * remove relation by id
  239. *
  240. * @static
  241. * @param {ObjectId} id
  242. * @returns {Promise<any>}
  243. * @memberof UserGroupRelation
  244. */
  245. schema.statics.removeById = function(id) {
  246. return this.findById(id)
  247. .then((relationData) => {
  248. if (relationData == null) {
  249. throw new Error('UserGroupRelation data is not exists. id:', id);
  250. }
  251. else {
  252. relationData.remove();
  253. }
  254. });
  255. };
  256. schema.statics.findUserIdsByGroupId = async function(groupId) {
  257. const relations = await this.find({ relatedGroup: groupId }, { _id: 0, relatedUser: 1 }).lean().exec(); // .lean() to get not ObjectId but string
  258. return relations.map(relation => relation.relatedUser);
  259. };
  260. schema.statics.createByGroupIdsAndUserIds = async function(groupIds, userIds) {
  261. const insertOperations: any[] = [];
  262. groupIds.forEach((groupId) => {
  263. userIds.forEach((userId) => {
  264. insertOperations.push({
  265. insertOne: {
  266. document: {
  267. relatedGroup: groupId,
  268. relatedUser: userId,
  269. },
  270. },
  271. });
  272. });
  273. });
  274. await this.bulkWrite(insertOperations);
  275. };
  276. /**
  277. * Recursively finds descendant groups by populating relations.
  278. * @static
  279. * @param {UserGroupDocument} group
  280. * @param {UserDocument} user
  281. * @returns UserGroupDocument[]
  282. */
  283. schema.statics.findGroupsWithDescendantsByGroupAndUser = async function(group: UserGroupDocument, user): Promise<UserGroupDocument[]> {
  284. const descendantGroups = [group];
  285. const incrementGroupsRecursively = async(groups, user) => {
  286. const groupIds = groups.map(g => g._id);
  287. const populatedRelations = await this.aggregate([
  288. {
  289. $match: {
  290. relatedUser: user._id,
  291. },
  292. },
  293. {
  294. $lookup: {
  295. from: this.collection.collectionName,
  296. localField: 'relatedGroup',
  297. foreignField: '_id',
  298. as: 'relatedGroup',
  299. },
  300. },
  301. {
  302. $unwind: {
  303. path: '$relatedGroup',
  304. },
  305. },
  306. {
  307. $match: {
  308. 'relatedGroup.parent': { $in: groupIds },
  309. },
  310. },
  311. ]);
  312. const nextGroups = populatedRelations.map(d => d.relatedGroup);
  313. // End
  314. const shouldEnd = nextGroups.length === 0;
  315. if (shouldEnd) {
  316. return;
  317. }
  318. // Increment
  319. descendantGroups.push(...nextGroups);
  320. return incrementGroupsRecursively(nextGroups, user);
  321. };
  322. await incrementGroupsRecursively([group], user);
  323. return descendantGroups;
  324. };
  325. export default getOrCreateModel<UserGroupRelationDocument, UserGroupRelationModel>('UserGroupRelation', schema);