user-group-relation.ts 10 KB

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