user-group-relation.ts 10 KB

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