Просмотр исходного кода

re-define consts for User model

Yuki Takei 2 месяцев назад
Родитель
Сommit
557706b85e

+ 2 - 2
apps/app/src/pages/common-props/commons.ts

@@ -154,12 +154,12 @@ export const getServerSideCommonEachProps = async (
 
   let currentUser: IUserHasId | undefined;
   if (user != null) {
-    const User = mongoose.model('User');
+    const User = mongoose.model<IUserHasId>('User');
     const userData = await User.findById(user.id).populate({
       path: 'imageAttachment',
       select: 'filePathProxied',
     });
-    currentUser = userData.toObject();
+    currentUser = userData?.toObject();
   }
 
   // Redirect destination for page transition by next/link

+ 3 - 3
apps/app/src/server/middlewares/login-required.js

@@ -1,6 +1,8 @@
 import { createRedirectToForUnauthenticated } from '~/server/util/createRedirectToForUnauthenticated';
 import loggerFactory from '~/utils/logger';
 
+import { UserStatus } from '../models/user/conts';
+
 const logger = loggerFactory('growi:middleware:login-required');
 
 /**
@@ -11,11 +13,9 @@ const logger = loggerFactory('growi:middleware:login-required');
  */
 module.exports = (crowi, isGuestAllowed = false, fallback = null) => {
   return (req, res, next) => {
-    const User = crowi.model('User');
-
     // check the user logged in
     if (req.user != null && req.user instanceof Object && '_id' in req.user) {
-      if (req.user.status === User.STATUS_ACTIVE) {
+      if (req.user.status === UserStatus.STATUS_ACTIVE) {
         // Active の人だけ先に進める
         return next();
       }

+ 2 - 1
apps/app/src/server/models/external-account.ts

@@ -13,6 +13,7 @@ import { NullUsernameToBeRegisteredError } from '~/server/models/errors';
 import loggerFactory from '~/utils/logger';
 
 import { getOrCreateModel } from '../util/mongoose-utils';
+import { UserStatus } from './user/conts';
 
 const logger = loggerFactory('growi:models:external-account');
 
@@ -127,7 +128,7 @@ schema.statics.findOrRegister = function (
           mailToBeRegistered,
           undefined,
           undefined,
-          User.STATUS_ACTIVE,
+          UserStatus.STATUS_ACTIVE,
         );
       })
       .then((newUser) => {

+ 3 - 3
apps/app/src/server/models/obsolete-page.js

@@ -14,6 +14,7 @@ import ExternalUserGroupRelation from '~/features/external-user-group/server/mod
 import loggerFactory from '~/utils/logger';
 
 import { configManager } from '../service/config-manager';
+import { USER_FIELDS_EXCEPT_CONFIDENTIAL } from './user/conts';
 import UserGroup from './user-group';
 import UserGroupRelation from './user-group-relation';
 
@@ -269,7 +270,7 @@ export const getPageSchema = (crowi) => {
     const User = crowi.model('User');
     return populateDataToShowRevision(
       this,
-      User.USER_FIELDS_EXCEPT_CONFIDENTIAL,
+      USER_FIELDS_EXCEPT_CONFIDENTIAL,
       shouldExcludeBody,
     );
   };
@@ -525,7 +526,6 @@ export const getPageSchema = (crowi) => {
   ) {
     validateCrowi();
 
-    const User = crowi.model('User');
     const opt = Object.assign({ sort: 'updatedAt', desc: -1 }, option);
     const sortOpt = {};
     sortOpt[opt.sort] = opt.desc;
@@ -547,7 +547,7 @@ export const getPageSchema = (crowi) => {
 
     // find
     builder.addConditionToPagenate(opt.offset, opt.limit, sortOpt);
-    builder.populateDataToList(User.USER_FIELDS_EXCEPT_CONFIDENTIAL);
+    builder.populateDataToList(USER_FIELDS_EXCEPT_CONFIDENTIAL);
     const pages = await builder.query.lean().clone().exec('find');
     const result = {
       pages,

+ 4 - 3
apps/app/src/server/models/page.ts

@@ -11,6 +11,7 @@ import {
 } from '@growi/core/dist/utils/path-utils';
 import assert from 'assert';
 import escapeStringRegexp from 'escape-string-regexp';
+import type mongoose from 'mongoose';
 import type {
   AnyObject,
   Document,
@@ -18,7 +19,7 @@ import type {
   Model,
   Types,
 } from 'mongoose';
-import mongoose, { Schema } from 'mongoose';
+import { Schema } from 'mongoose';
 import mongoosePaginate from 'mongoose-paginate-v2';
 import uniqueValidator from 'mongoose-unique-validator';
 import nodePath from 'path';
@@ -40,6 +41,7 @@ import {
   getPageSchema,
   populateDataToShowRevision,
 } from './obsolete-page';
+import { USER_FIELDS_EXCEPT_CONFIDENTIAL } from './user/conts';
 import type { UserGroupDocument } from './user-group';
 import UserGroupRelation from './user-group-relation';
 
@@ -911,7 +913,6 @@ schema.statics.findRecentUpdatedPages = async function (
 ): Promise<PaginatedPages> {
   const sortOpt = {};
   sortOpt[options.sort] = options.desc;
-  const User = mongoose.model('User') as any;
 
   if (path == null) {
     throw new Error('path is required.');
@@ -929,7 +930,7 @@ schema.statics.findRecentUpdatedPages = async function (
   }
 
   queryBuilder.addConditionToListWithDescendants(path, options);
-  queryBuilder.populateDataToList(User.USER_FIELDS_EXCEPT_CONFIDENTIAL);
+  queryBuilder.populateDataToList(USER_FIELDS_EXCEPT_CONFIDENTIAL);
   await queryBuilder.addViewerCondition(
     user,
     undefined,

+ 4 - 3
apps/app/src/server/models/user-group-relation.ts

@@ -1,5 +1,5 @@
 import { getIdForRef, isPopulated } from '@growi/core';
-import type { IUserGroupRelation } from '@growi/core/dist/interfaces';
+import type { IUser, IUserGroupRelation } from '@growi/core/dist/interfaces';
 import type { Document, Model } from 'mongoose';
 import mongoose, { Schema } from 'mongoose';
 import mongoosePaginate from 'mongoose-paginate-v2';
@@ -9,6 +9,7 @@ import loggerFactory from '~/utils/logger';
 
 import type { ObjectIdLike } from '../interfaces/mongoose-utils';
 import { getOrCreateModel } from '../util/mongoose-utils';
+import { UserStatus } from './user/conts';
 import type { UserGroupDocument } from './user-group';
 
 const logger = loggerFactory('growi:models:userGroupRelation');
@@ -207,7 +208,7 @@ schema.statics.countByGroupIdsAndUser = async function (
  * @memberof UserGroupRelation
  */
 schema.statics.findUserByNotRelatedGroup = function (userGroup, queryOptions) {
-  const User = mongoose.model('User') as any;
+  const User = mongoose.model<IUser>('User');
   let searchWord = new RegExp(`${queryOptions.searchWord}`);
   switch (queryOptions.searchType) {
     case 'forward':
@@ -231,7 +232,7 @@ schema.statics.findUserByNotRelatedGroup = function (userGroup, queryOptions) {
     });
     const query = {
       _id: { $nin: relatedUserIds },
-      status: User.STATUS_ACTIVE,
+      status: UserStatus.STATUS_ACTIVE,
       $or: searthField,
     };
 

+ 12 - 0
apps/app/src/server/models/user/conts.ts

@@ -0,0 +1,12 @@
+export const UserStatus = {
+  STATUS_REGISTERED: 1,
+  STATUS_ACTIVE: 2,
+  STATUS_SUSPENDED: 3,
+  STATUS_DELETED: 4,
+  STATUS_INVITED: 5,
+} as const;
+export type UserStatus = (typeof UserStatus)[keyof typeof UserStatus];
+
+export const USER_FIELDS_EXCEPT_CONFIDENTIAL =
+  '_id image isEmailPublished isGravatarEnabled googleId name username email introduction' +
+  ' status lang createdAt lastLoginAt admin imageUrlCached';

+ 20 - 34
apps/app/src/server/models/user.js → apps/app/src/server/models/user/index.js

@@ -10,6 +10,7 @@ import { aclService } from '../service/acl';
 import { configManager } from '../service/config-manager';
 import { getModelSafely } from '../util/mongoose-utils';
 import { Attachment } from './attachment';
+import { USER_FIELDS_EXCEPT_CONFIDENTIAL, UserStatus } from './conts';
 
 const crypto = require('crypto');
 
@@ -26,15 +27,6 @@ const factory = (crowi) => {
     return userModelExists;
   }
 
-  const STATUS_REGISTERED = 1;
-  const STATUS_ACTIVE = 2;
-  const STATUS_SUSPENDED = 3;
-  const STATUS_DELETED = 4;
-  const STATUS_INVITED = 5;
-  const USER_FIELDS_EXCEPT_CONFIDENTIAL =
-    '_id image isEmailPublished isGravatarEnabled googleId name username email introduction' +
-    ' status lang createdAt lastLoginAt admin imageUrlCached';
-
   const PAGE_ITEMS = 50;
 
   let userEvent;
@@ -77,7 +69,7 @@ const factory = (crowi) => {
       status: {
         type: Number,
         required: true,
-        default: STATUS_ACTIVE,
+        default: UserStatus.STATUS_ACTIVE,
         index: true,
       },
       lastLoginAt: { type: Date, index: true },
@@ -110,7 +102,7 @@ const factory = (crowi) => {
 
     const isInstalled = configManager.getConfig('app:installed');
     if (!isInstalled) {
-      return STATUS_ACTIVE; // is this ok?
+      return UserStatus.STATUS_ACTIVE; // is this ok?
     }
 
     // status decided depends on registrationMode
@@ -119,12 +111,12 @@ const factory = (crowi) => {
     );
     switch (registrationMode) {
       case aclService.labels.SECURITY_REGISTRATION_MODE_OPEN:
-        return STATUS_ACTIVE;
+        return UserStatus.STATUS_ACTIVE;
       case aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED:
       case aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED: // 一応
-        return STATUS_REGISTERED;
+        return UserStatus.STATUS_REGISTERED;
       default:
-        return STATUS_ACTIVE; // どっちにすんのがいいんだろうな
+        return UserStatus.STATUS_ACTIVE; // どっちにすんのがいいんだろうな
     }
   }
 
@@ -289,7 +281,7 @@ const factory = (crowi) => {
     this.setPassword(password);
     this.name = name;
     this.username = username;
-    this.status = STATUS_ACTIVE;
+    this.status = UserStatus.STATUS_ACTIVE;
     this.isEmailPublished = configManager.getConfig(
       'customize:isEmailPublishedForNewUser',
     );
@@ -335,14 +327,14 @@ const factory = (crowi) => {
 
   userSchema.methods.statusActivate = async function () {
     logger.debug('Activate User', this);
-    this.status = STATUS_ACTIVE;
+    this.status = UserStatus.STATUS_ACTIVE;
     const userData = await this.save();
     return userEvent.emit('activated', userData);
   };
 
   userSchema.methods.statusSuspend = async function () {
     logger.debug('Suspend User', this);
-    this.status = STATUS_SUSPENDED;
+    this.status = UserStatus.STATUS_SUSPENDED;
     if (this.email === undefined || this.email === null) {
       // migrate old data
       this.email = '-';
@@ -364,7 +356,7 @@ const factory = (crowi) => {
     const now = new Date();
     const deletedLabel = `deleted_at_${now.getTime()}`;
 
-    this.status = STATUS_DELETED;
+    this.status = UserStatus.STATUS_DELETED;
     this.username = deletedLabel;
     this.password = '';
     this.name = '';
@@ -417,7 +409,10 @@ const factory = (crowi) => {
     const sort = option.sort || { createdAt: -1 };
     const fields = option.fields || {};
 
-    let status = option.status || [STATUS_ACTIVE, STATUS_SUSPENDED];
+    let status = option.status || [
+      UserStatus.STATUS_ACTIVE,
+      UserStatus.STATUS_SUSPENDED,
+    ];
     if (!Array.isArray(status)) {
       status = [status];
     }
@@ -434,7 +429,7 @@ const factory = (crowi) => {
 
   userSchema.statics.findUsersByIds = function (ids, option = {}) {
     const sort = option.sort || { createdAt: -1 };
-    const status = option.status || STATUS_ACTIVE;
+    const status = option.status || UserStatus.STATUS_ACTIVE;
     const fields = option.fields || {};
 
     return this.find({ _id: { $in: ids }, status })
@@ -445,7 +440,7 @@ const factory = (crowi) => {
   userSchema.statics.findAdmins = async function (option) {
     const sort = option?.sort ?? { createdAt: -1 };
 
-    let status = option?.status ?? [STATUS_ACTIVE];
+    let status = option?.status ?? [UserStatus.STATUS_ACTIVE];
     if (!Array.isArray(status)) {
       status = [status];
     }
@@ -511,7 +506,7 @@ const factory = (crowi) => {
   };
 
   userSchema.statics.countActiveUsers = async function () {
-    return this.countListByStatus(STATUS_ACTIVE);
+    return this.countListByStatus(UserStatus.STATUS_ACTIVE);
   };
 
   userSchema.statics.countListByStatus = async function (status) {
@@ -598,7 +593,7 @@ const factory = (crowi) => {
     newUser.username = tmpUsername;
     newUser.email = email;
     newUser.setPassword(password);
-    newUser.status = STATUS_INVITED;
+    newUser.status = UserStatus.STATUS_INVITED;
 
     const globalLang = configManager.getConfig('app:globalLang');
     if (globalLang != null) {
@@ -628,7 +623,7 @@ const factory = (crowi) => {
     // check exists and get list of try to create
     const existingUserList = await User.find({
       email: { $in: emailList },
-      userStatus: { $ne: STATUS_DELETED },
+      status: { $ne: UserStatus.STATUS_DELETED },
     });
     const existingEmailList = existingUserList.map((user) => {
       return user.email;
@@ -715,7 +710,7 @@ const factory = (crowi) => {
         return callback(err);
       }
 
-      if (userData.status === STATUS_ACTIVE) {
+      if (userData.status === UserStatus.STATUS_ACTIVE) {
         userEvent.emit('activated', userData);
       }
       return callback(err, userData);
@@ -855,15 +850,6 @@ const factory = (crowi) => {
     }
   }
 
-  userSchema.statics.STATUS_REGISTERED = STATUS_REGISTERED;
-  userSchema.statics.STATUS_ACTIVE = STATUS_ACTIVE;
-  userSchema.statics.STATUS_SUSPENDED = STATUS_SUSPENDED;
-  userSchema.statics.STATUS_DELETED = STATUS_DELETED;
-  userSchema.statics.STATUS_INVITED = STATUS_INVITED;
-  userSchema.statics.USER_FIELDS_EXCEPT_CONFIDENTIAL =
-    USER_FIELDS_EXCEPT_CONFIDENTIAL;
-  userSchema.statics.PAGE_ITEMS = PAGE_ITEMS;
-
   return mongoose.model('User', userSchema);
 };
 

+ 3 - 2
apps/app/src/server/routes/apiv3/security-settings/checkSetupStrategiesHasAdmin.ts

@@ -1,6 +1,7 @@
 import mongoose from 'mongoose';
 
 import type { IExternalAuthProviderType } from '~/interfaces/external-auth-provider';
+import { UserStatus } from '~/server/models/user/conts';
 
 interface AggregateResult {
   count: number;
@@ -13,7 +14,7 @@ const checkLocalStrategyHasAdmin = async (): Promise<boolean> => {
     {
       $match: {
         admin: true,
-        status: User.STATUS_ACTIVE,
+        status: UserStatus.STATUS_ACTIVE,
         password: { $exists: true },
       },
     },
@@ -29,7 +30,7 @@ const checkExternalStrategiesHasAdmin = async (
   const User = mongoose.model('User') as any;
 
   const externalAdmins: AggregateResult[] = await User.aggregate([
-    { $match: { admin: true, status: User.STATUS_ACTIVE } },
+    { $match: { admin: true, status: UserStatus.STATUS_ACTIVE } },
     {
       $lookup: {
         from: 'externalaccounts',

+ 9 - 8
apps/app/src/server/routes/apiv3/users.js

@@ -15,6 +15,7 @@ import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import Activity from '~/server/models/activity';
 import ExternalAccount from '~/server/models/external-account';
 import { serializePageSecurely } from '~/server/models/serializers';
+import { UserStatus } from '~/server/models/user/conts';
 import UserGroupRelation from '~/server/models/user-group-relation';
 import { configManager } from '~/server/service/config-manager';
 import { growiInfoService } from '~/server/service/growi-info';
@@ -124,10 +125,10 @@ module.exports = (crowi) => {
   const { User, Page } = crowi.models;
 
   const statusNo = {
-    registered: User.STATUS_REGISTERED,
-    active: User.STATUS_ACTIVE,
-    suspended: User.STATUS_SUSPENDED,
-    invited: User.STATUS_INVITED,
+    registered: UserStatus.STATUS_REGISTERED,
+    active: UserStatus.STATUS_ACTIVE,
+    suspended: UserStatus.STATUS_SUSPENDED,
+    invited: UserStatus.STATUS_INVITED,
   };
 
   validator.statusList = [
@@ -1535,7 +1536,7 @@ module.exports = (crowi) => {
           const activeUserData =
             await User.findUserByUsernameRegexWithTotalCount(
               q,
-              [User.STATUS_ACTIVE],
+              [UserStatus.STATUS_ACTIVE],
               { offset, limit },
             );
           const activeUsernames = activeUserData.users.map(
@@ -1551,9 +1552,9 @@ module.exports = (crowi) => {
 
         if (options.isIncludeInactiveUser) {
           const inactiveUserStates = [
-            User.STATUS_REGISTERED,
-            User.STATUS_SUSPENDED,
-            User.STATUS_INVITED,
+            UserStatus.STATUS_REGISTERED,
+            UserStatus.STATUS_SUSPENDED,
+            UserStatus.STATUS_INVITED,
           ];
           const inactiveUserData =
             await User.findUserByUsernameRegexWithTotalCount(

+ 2 - 1
apps/app/src/server/routes/login.js

@@ -2,6 +2,7 @@ import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 
+import { UserStatus } from '../models/user/conts';
 import { growiInfoService } from '../service/growi-info';
 
 // disable all of linting
@@ -157,7 +158,7 @@ module.exports = (crowi, app) => {
   actions.preLogin = (req, res, next) => {
     // user has already logged in
     const { user } = req;
-    if (user != null && user.status === User.STATUS_ACTIVE) {
+    if (user != null && user.status === UserStatus.STATUS_ACTIVE) {
       const { redirectTo } = req.session;
       // remove session.redirectTo
       delete req.session.redirectTo;

+ 33 - 33
apps/app/src/server/service/page/index.ts

@@ -524,7 +524,7 @@ class PageService implements IPageService {
   }
 
   private shouldUseV4ProcessForRevert(page): boolean {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const isV5Compatible = configManager.getConfig('app:isV5Compatible');
     const isPageRestricted = page.grant === Page.GRANT_RESTRICTED;
@@ -535,7 +535,7 @@ class PageService implements IPageService {
   }
 
   private shouldNormalizeParent(page): boolean {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     return (
       page.grant !== Page.GRANT_RESTRICTED &&
@@ -573,7 +573,7 @@ class PageService implements IPageService {
     /*
      * Common Operation
      */
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const parameters = {
       ip: activityParameters.ip,
@@ -805,7 +805,7 @@ class PageService implements IPageService {
     pageOpId: ObjectIdLike,
     activity?,
   ): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const exParentId = page.parent;
 
@@ -938,7 +938,7 @@ class PageService implements IPageService {
   }
 
   private async getParentAndforceCreateEmptyTree(originalPage, toPath: string) {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const fromPath = originalPage.path;
     const newParentPath = pathlib.dirname(toPath);
@@ -1075,7 +1075,7 @@ class PageService implements IPageService {
       );
     }
 
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const { updateMetadata, createRedirectPage } = options;
 
@@ -1351,7 +1351,7 @@ class PageService implements IPageService {
       throw new Error('Cannot find or duplicate the empty page');
     }
 
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     if (!isRecursively && page.isEmpty) {
       throw Error('Page not found.');
@@ -1807,7 +1807,7 @@ class PageService implements IPageService {
     oldPagePathPrefix,
     newPagePathPrefix,
   ) {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const pageIds = pages.map((page) => page._id);
     const revisions = await Revision.find({ pageId: { $in: pageIds } });
@@ -2127,7 +2127,7 @@ class PageService implements IPageService {
   }
 
   private async deleteNonEmptyTarget(page, user) {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const newPath = Page.getDeletedPageName(page.path);
 
     const deletedPage = await Page.findByIdAndUpdate(
@@ -2248,7 +2248,7 @@ class PageService implements IPageService {
   }
 
   private async deleteDescendants(pages, user) {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const deletePageOperations: any[] = [];
     const insertPageRedirectOperations: any[] = [];
@@ -2370,7 +2370,7 @@ class PageService implements IPageService {
 
   async deleteCompletelyOperation(pageIds, pagePaths): Promise<void> {
     // Delete Attachments, Revisions, Pages and emit delete
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const { attachmentService } = this.crowi;
     const attachments = await Attachment.find({ page: { $in: pageIds } });
@@ -2935,7 +2935,7 @@ class PageService implements IPageService {
     pageOpId: ObjectIdLike,
     activity?,
   ): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const descendantsSubscribedSets = new Set();
     await this.revertDeletedDescendantsWithStream(
@@ -2992,7 +2992,7 @@ class PageService implements IPageService {
     newPath: string,
     pageOpId: ObjectIdLike,
   ): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const newTarget = await Page.findOne({ path: newPath }); // only one page will be found since duplicating to existing path is forbidden
 
@@ -3112,7 +3112,7 @@ class PageService implements IPageService {
     userRelatedGroups: PopulatedGrantedGroup[],
     userRelatedParentGrantedGroups: IGrantedGroup[],
   ): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const operations: any = [];
 
     pages.forEach((childPage) => {
@@ -3318,7 +3318,7 @@ class PageService implements IPageService {
     pageIds: ObjectIdLike[] = [],
     user?,
   ): Promise<Record<string, string | null>> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const MAX_LENGTH = 350;
 
     // aggregation options
@@ -3513,7 +3513,7 @@ class PageService implements IPageService {
     pageIds: ObjectIdLike[],
     user,
   ): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const pages = await Page.findByIdsAndViewer(pageIds, user, null);
 
@@ -3533,7 +3533,7 @@ class PageService implements IPageService {
   }
 
   async normalizeParentByPageIds(pageIds: ObjectIdLike[], user): Promise<void> {
-    const Page = (await mongoose.model('Page')) as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const socket = this.crowi.socketIoService.getDefaultSocket();
 
@@ -3574,7 +3574,7 @@ class PageService implements IPageService {
   }
 
   private async normalizeParentByPage(page, user) {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const {
       path,
@@ -3711,7 +3711,7 @@ class PageService implements IPageService {
         );
       }
 
-      const Page = mongoose.model('Page') as unknown as PageModel;
+      const Page = mongoose.model<IPage, PageModel>('Page');
       const { PageQueryBuilder } = Page;
       const builder = new PageQueryBuilder(Page.findOne());
       builder.addConditionAsOnTree();
@@ -3773,7 +3773,7 @@ class PageService implements IPageService {
     pageOpId: ObjectIdLike,
   ): Promise<number> {
     // Save prevDescendantCount for sub-operation
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const { PageQueryBuilder } = Page;
     const builder = new PageQueryBuilder(Page.findOne(), true);
     builder.addConditionAsOnTree();
@@ -3816,7 +3816,7 @@ class PageService implements IPageService {
     pageOpId: ObjectIdLike,
     options: { prevDescendantCount: number },
   ): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     try {
       // update descendantCount of self and descendant pages first
@@ -3855,7 +3855,7 @@ class PageService implements IPageService {
   }
 
   async _isPagePathIndexUnique() {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const now = new Date().toString();
     const path = `growi_check_is_path_index_unique_${now}`;
 
@@ -3953,7 +3953,7 @@ class PageService implements IPageService {
     isDuplicateOperation = false,
     shouldEmitProgress = false,
   ): Promise<number> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const ancestorPaths = paths.flatMap((p) => collectAncestorPaths(p, []));
     // targets' descendants
@@ -3992,7 +3992,7 @@ class PageService implements IPageService {
     publicPathsToNormalize: string[],
     grantFiltersByUser?: { $or: any[] } | null,
   ) {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const andFilter: any = {
       $and: [
@@ -4051,7 +4051,7 @@ class PageService implements IPageService {
       ? this.crowi.socketIoService.getAdminSocket()
       : null;
 
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const { PageQueryBuilder } = Page;
 
     // Build filter
@@ -4295,7 +4295,7 @@ class PageService implements IPageService {
       throw Error('user is required');
     }
 
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const { PageQueryBuilder } = Page;
 
     const builder = new PageQueryBuilder(Page.count(), false);
@@ -4418,7 +4418,7 @@ class PageService implements IPageService {
   ): any[] {
     const aggregationPipeline: any[] = [];
 
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     // -- Filter by paths
     aggregationPipeline.push({ $match: { path: { $in: paths } } });
@@ -4448,7 +4448,7 @@ class PageService implements IPageService {
     onlyMigratedAsExistingPages = true,
     andFilter?,
   ): Promise<any[]> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const pipeline = this.buildBasePipelineToCreateEmptyPages(
       paths,
@@ -4475,7 +4475,7 @@ class PageService implements IPageService {
   }
 
   private async connectPageTree(path: string): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
     const { PageQueryBuilder } = Page;
 
     const ancestorPaths = collectAncestorPaths(path);
@@ -4622,7 +4622,7 @@ class PageService implements IPageService {
     path: string,
     user?,
   ): void {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     pageDocument.path = path;
     pageDocument.creator = user;
@@ -4671,7 +4671,7 @@ class PageService implements IPageService {
     user?,
     options?: IOptionsForCreate,
   ): Promise<boolean> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     // Operatability validation
     const canOperate = await this.crowi.pageOperationService.canOperate(
@@ -5011,7 +5011,7 @@ class PageService implements IPageService {
     body: string,
     options: IOptionsForCreate & { grantUserIds?: ObjectIdLike[] },
   ): Promise<PageDocument> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     const isV5Compatible = configManager.getConfig('app:isV5Compatible');
     if (!isV5Compatible) {
@@ -5496,7 +5496,7 @@ class PageService implements IPageService {
     user: IUserHasId,
     userGroups = null,
   ): Promise<HydratedDocument<IPage>[]> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Page = mongoose.model<IPage, PageModel>('Page');
 
     // https://regex101.com/r/KYZWls/1
     // ex. /trash/.*

+ 3 - 2
apps/app/src/server/service/pre-notify.ts

@@ -3,6 +3,7 @@ import mongoose from 'mongoose';
 
 import type { ActivityDocument } from '../models/activity';
 import Subscription from '../models/subscription';
+import { UserStatus } from '../models/user/conts';
 
 export type PreNotifyProps = {
   notificationTargetUsers?: Ref<IUser>[];
@@ -38,7 +39,7 @@ class PreNotifyService implements IPreNotifyService {
     const preNotify = async (props: PreNotifyProps) => {
       const { notificationTargetUsers } = props;
 
-      const User = mongoose.model<IUser, { find; STATUS_ACTIVE }>('User');
+      const User = mongoose.model<IUser, { find }>('User');
       const actionUser = activity.user;
       const target = activity.target;
       const subscribedUsers = await Subscription.getSubscription(
@@ -49,7 +50,7 @@ class PreNotifyService implements IPreNotifyService {
       );
       const activeNotificationUsers = await User.find({
         _id: { $in: notificationUsers },
-        status: User.STATUS_ACTIVE,
+        status: UserStatus.STATUS_ACTIVE,
       }).distinct('_id');
 
       if (getAdditionalTargetUsers == null) {

+ 5 - 5
apps/app/src/server/service/search.ts

@@ -1,4 +1,4 @@
-import type { IPageHasId } from '@growi/core';
+import type { IPage, IPageHasId, IUser } from '@growi/core';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import mongoose from 'mongoose';
 import { FilterXSS } from 'xss';
@@ -14,6 +14,7 @@ import type {
   IPageWithSearchMeta,
   ISearchResult,
 } from '~/interfaces/search';
+import { USER_FIELDS_EXCEPT_CONFIDENTIAL } from '~/server/models/user/conts';
 import loggerFactory from '~/utils/logger';
 
 import type Crowi from '../crowi';
@@ -60,8 +61,7 @@ const normalizeNQName = (nqName: string): string => {
 };
 
 const findPageListByIds = async (pageIds: ObjectIdLike[], crowi: any) => {
-  const Page = mongoose.model('Page') as unknown as PageModel;
-  const User = mongoose.model('User') as any; // Cast to any to access static properties
+  const Page = mongoose.model<IPage, PageModel>('Page');
 
   const builder = new Page.PageQueryBuilder(
     Page.find({ _id: { $in: pageIds } }),
@@ -70,10 +70,10 @@ const findPageListByIds = async (pageIds: ObjectIdLike[], crowi: any) => {
 
   builder.addConditionToPagenate(undefined, undefined); // offset and limit are unnesessary
 
-  builder.populateDataToList(User.USER_FIELDS_EXCEPT_CONFIDENTIAL); // populate lastUpdateUser
+  builder.populateDataToList(USER_FIELDS_EXCEPT_CONFIDENTIAL); // populate lastUpdateUser
   builder.query = builder.query.populate({
     path: 'creator',
-    select: User.USER_FIELDS_EXCEPT_CONFIDENTIAL,
+    select: USER_FIELDS_EXCEPT_CONFIDENTIAL,
   });
 
   const pages = await builder.query.clone().exec('find');

+ 0 - 1
apps/app/src/server/service/slack-event-handler/link-shared.ts

@@ -179,7 +179,6 @@ export class LinkSharedEventHandler
     pages: any,
     isPermalink: boolean,
   ): DataForUnfurl[] {
-    const Page = mongoose.model('Page') as unknown as PageModel;
     const unfurlData: DataForUnfurl[] = [];
 
     for (const page of pages) {

+ 3 - 2
apps/app/test/integration/middlewares/login-required.test.js

@@ -1,3 +1,4 @@
+const { UserStatus } = require('../../../src/server/models/user/conts');
 const { getInstance } = require('../setup-crowi');
 
 describe('loginRequired', () => {
@@ -231,7 +232,7 @@ describe('loginRequired', () => {
 
       req.user = {
         _id: 'user id',
-        status: User.STATUS_ACTIVE,
+        status: UserStatus.STATUS_ACTIVE,
       };
 
       const result = loginRequiredStrictly(req, res, next);
@@ -277,7 +278,7 @@ describe('loginRequired', () => {
       req.baseUrl = '/path/that/requires/loggedin';
       req.user = {
         _id: 'user id',
-        status: User.STATUS_DELETED,
+        status: UserStatus.STATUS_DELETED,
       };
 
       const result = loginRequiredStrictly(req, res, next);

+ 5 - 4
apps/app/test/integration/models/user.test.js

@@ -1,6 +1,7 @@
 const mongoose = require('mongoose');
 
 const { getInstance } = require('../setup-crowi');
+const { UserStatus } = require('../../../src/server/models/user/conts');
 
 describe('User', () => {
   // biome-ignore lint/correctness/noUnusedVariables: ignore
@@ -27,7 +28,7 @@ describe('User', () => {
         email: 'adminusertest1@example.com',
         password: 'adminusertestpass',
         admin: true,
-        status: User.STATUS_ACTIVE,
+        status: UserStatus.STATUS_ACTIVE,
         lang: 'en_US',
       },
       {
@@ -36,7 +37,7 @@ describe('User', () => {
         email: 'adminusertes2@example.com',
         password: 'adminusertestpass',
         admin: true,
-        status: User.STATUS_SUSPENDED,
+        status: UserStatus.STATUS_SUSPENDED,
         lang: 'en_US',
       },
       {
@@ -45,7 +46,7 @@ describe('User', () => {
         email: 'adminusertestToBeRemoved@example.com',
         password: 'adminusertestpass',
         admin: true,
-        status: User.STATUS_ACTIVE,
+        status: UserStatus.STATUS_ACTIVE,
         lang: 'en_US',
       },
     ]);
@@ -121,7 +122,7 @@ describe('User', () => {
 
     test("with 'includesInactive' option should retrieves suspended users", async () => {
       const users = await User.findAdmins({
-        status: [User.STATUS_ACTIVE, User.STATUS_SUSPENDED],
+        status: [UserStatus.STATUS_ACTIVE, UserStatus.STATUS_SUSPENDED],
       });
       const adminusertestActive = users.find(
         (user) => user.username === 'adminusertest1',