Browse Source

Merge branch 'feat/auditlog' of https://github.com/weseek/growi into feat/auditlog

Shun Miyazawa 3 years ago
parent
commit
f4facc29b4

+ 25 - 1
packages/app/src/server/models/activity.ts

@@ -30,7 +30,7 @@ export interface ActivityModel extends Model<ActivityDocument> {
 }
 }
 
 
 const snapshotSchema = new Schema<ISnapshot>({
 const snapshotSchema = new Schema<ISnapshot>({
-  username: { type: String },
+  username: { type: String, index: true },
 });
 });
 
 
 // TODO: add revision id
 // TODO: add revision id
@@ -116,4 +116,28 @@ activitySchema.statics.getPaginatedActivity = async function(limit: number, offs
   return paginateResult;
   return paginateResult;
 };
 };
 
 
+activitySchema.statics.findSnapshotUsernamesByUsernameRegexWithTotalCount = async function(
+    q: string, option: { sortOpt: number | string, offset: number, limit: number},
+): Promise<{usernames: string[], totalCount: number}> {
+  const opt = option || {};
+  const sortOpt = opt.sortOpt || 1;
+  const offset = opt.offset || 0;
+  const limit = opt.limit || 10;
+
+  const conditions = { 'snapshot.username': { $regex: q, $options: 'i' } };
+
+  const usernames = await this.aggregate()
+    .skip(0)
+    .limit(10000) // Narrow down the search target
+    .match(conditions)
+    .group({ _id: '$snapshot.username' })
+    .sort({ _id: sortOpt }) // Sort "snapshot.username" in ascending order
+    .skip(offset)
+    .limit(limit);
+
+  const totalCount = (await this.find(conditions).distinct('snapshot.username')).length;
+
+  return { usernames: usernames.map(r => r._id), totalCount };
+};
+
 export default getOrCreateModel<ActivityDocument, ActivityModel>('Activity', activitySchema);
 export default getOrCreateModel<ActivityDocument, ActivityModel>('Activity', activitySchema);

+ 5 - 0
packages/app/src/server/routes/apiv3/activity.ts

@@ -48,6 +48,11 @@ module.exports = (crowi: Crowi): Router => {
     try {
     try {
       const parsedSearchFilter = JSON.parse(req.query.searchFilter as string);
       const parsedSearchFilter = JSON.parse(req.query.searchFilter as string);
 
 
+      // add username to query
+      if (typeof parsedSearchFilter.username === 'string') {
+        Object.assign(query, { 'snapshot.username': parsedSearchFilter.username });
+      }
+
       // add action to query
       // add action to query
       const canContainActionFilterToQuery = parsedSearchFilter.action.every(a => AllSupportedActionType.includes(a));
       const canContainActionFilterToQuery = parsedSearchFilter.action.every(a => AllSupportedActionType.includes(a));
       if (canContainActionFilterToQuery) {
       if (canContainActionFilterToQuery) {

+ 11 - 3
packages/app/src/server/routes/apiv3/users.js

@@ -1,3 +1,4 @@
+import Activity from '~/server/models/activity';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
@@ -949,14 +950,21 @@ module.exports = (crowi) => {
       }
       }
 
 
       if (options.isIncludeInactiveUser) {
       if (options.isIncludeInactiveUser) {
-        const inactiveUserStates = [User.STATUS_REGISTERED, User.STATUS_SUSPENDED, User.STATUS_DELETED, User.STATUS_INVITED];
+        const inactiveUserStates = [User.STATUS_REGISTERED, User.STATUS_SUSPENDED, User.STATUS_INVITED];
         const inactiveUserData = await User.findUserByUsernameRegexWithTotalCount(q, inactiveUserStates, { offset, limit });
         const inactiveUserData = await User.findUserByUsernameRegexWithTotalCount(q, inactiveUserStates, { offset, limit });
         const inactiveUsernames = inactiveUserData.users.map(user => user.username);
         const inactiveUsernames = inactiveUserData.users.map(user => user.username);
         Object.assign(data, { inactiveUser: { usernames: inactiveUsernames, totalCount: inactiveUserData.totalCount } });
         Object.assign(data, { inactiveUser: { usernames: inactiveUsernames, totalCount: inactiveUserData.totalCount } });
       }
       }
 
 
-      if (options.isIncludeMixedUsername) {
-        const allUsernames = [...data.activeUser?.usernames || [], ...data.inactiveUser?.usernames || []];
+      if (options.isIncludeActivitySnapshotUser && req.user.admin) {
+        const activitySnapshotUserData = await Activity.findSnapshotUsernamesByUsernameRegexWithTotalCount(q, { offset, limit });
+        Object.assign(data, { activitySnapshotUser: activitySnapshotUserData });
+      }
+
+      // eslint-disable-next-line max-len
+      const canIncludeMixedUsernames = (options.isIncludeMixedUsernames && req.user.admin) || (options.isIncludeMixedUsernames && !options.isIncludeActivitySnapshotUser);
+      if (canIncludeMixedUsernames) {
+        const allUsernames = [...data.activeUser?.usernames || [], ...data.inactiveUser?.usernames || [], ...data?.activitySnapshotUser?.usernames || []];
         const distinctUsernames = Array.from(new Set(allUsernames));
         const distinctUsernames = Array.from(new Set(allUsernames));
         Object.assign(data, { mixedUsernames: distinctUsernames });
         Object.assign(data, { mixedUsernames: distinctUsernames });
       }
       }

+ 31 - 2
packages/app/src/stores/user.tsx

@@ -1,8 +1,8 @@
 import useSWR, { SWRResponse } from 'swr';
 import useSWR, { SWRResponse } from 'swr';
-import { apiv3Get } from '~/client/util/apiv3-client';
+import useSWRImmutable from 'swr/immutable';
 
 
+import { apiv3Get } from '~/client/util/apiv3-client';
 import { IUserHasId } from '~/interfaces/user';
 import { IUserHasId } from '~/interfaces/user';
-
 import { checkAndUpdateImageUrlCached } from '~/stores/middlewares/user';
 import { checkAndUpdateImageUrlCached } from '~/stores/middlewares/user';
 
 
 export const useSWRxUsersList = (userIds: string[]): SWRResponse<IUserHasId[], Error> => {
 export const useSWRxUsersList = (userIds: string[]): SWRResponse<IUserHasId[], Error> => {
@@ -15,3 +15,32 @@ export const useSWRxUsersList = (userIds: string[]): SWRResponse<IUserHasId[], E
     { use: [checkAndUpdateImageUrlCached] },
     { use: [checkAndUpdateImageUrlCached] },
   );
   );
 };
 };
+
+
+type usernameRequestOptions = {
+  isIncludeActiveUser?: boolean,
+  isIncludeInactiveUser?: boolean,
+  isIncludeActivitySnapshotUser?: boolean,
+  isIncludeMixedUsernames?: boolean,
+}
+
+type userData = {
+  usernames: string[]
+  totalCount: number
+}
+
+type usernameResult = {
+  activeUser?: userData
+  inactiveUser?: userData
+  activitySnapshotUser?: userData
+  mixedUsernames?: string[]
+}
+
+export const useSWRxUsernames = (q: string, offset?: number, limit?: number, options?: usernameRequestOptions): SWRResponse<usernameResult, Error> => {
+  return useSWRImmutable(
+    q != null ? ['/users/usernames', q, offset, limit, options] : null,
+    (endpoint, q, offset, limit, options) => apiv3Get(endpoint, {
+      q, offset, limit, options,
+    }).then(result => result.data),
+  );
+};