|
@@ -21,6 +21,7 @@ import { Comment } from '~/features/comment/server';
|
|
|
import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
|
|
import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
|
|
|
import { SupportedAction } from '~/interfaces/activity';
|
|
import { SupportedAction } from '~/interfaces/activity';
|
|
|
import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
|
|
import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
|
|
|
|
|
+import type { IOptionsForCreate, IOptionsForUpdate } from '~/interfaces/page';
|
|
|
import type { IPageDeleteConfigValueToProcessValidation } from '~/interfaces/page-delete-config';
|
|
import type { IPageDeleteConfigValueToProcessValidation } from '~/interfaces/page-delete-config';
|
|
|
import {
|
|
import {
|
|
|
PageDeleteConfigValue, PageSingleDeleteCompConfigValue,
|
|
PageDeleteConfigValue, PageSingleDeleteCompConfigValue,
|
|
@@ -30,8 +31,9 @@ import {
|
|
|
type IPageOperationProcessInfo, type IPageOperationProcessData, PageActionStage, PageActionType,
|
|
type IPageOperationProcessInfo, type IPageOperationProcessData, PageActionStage, PageActionType,
|
|
|
} from '~/interfaces/page-operation';
|
|
} from '~/interfaces/page-operation';
|
|
|
import { SocketEventName, type PageMigrationErrorData, type UpdateDescCountRawData } from '~/interfaces/websocket';
|
|
import { SocketEventName, type PageMigrationErrorData, type UpdateDescCountRawData } from '~/interfaces/websocket';
|
|
|
|
|
+import type { CreateMethod } from '~/server/models/page';
|
|
|
import {
|
|
import {
|
|
|
- type CreateMethod, type PageCreateOptions, type PageModel, type PageDocument, pushRevision, PageQueryBuilder,
|
|
|
|
|
|
|
+ type PageModel, type PageDocument, pushRevision, PageQueryBuilder,
|
|
|
} from '~/server/models/page';
|
|
} from '~/server/models/page';
|
|
|
import type { PageTagRelationDocument } from '~/server/models/page-tag-relation';
|
|
import type { PageTagRelationDocument } from '~/server/models/page-tag-relation';
|
|
|
import PageTagRelation from '~/server/models/page-tag-relation';
|
|
import PageTagRelation from '~/server/models/page-tag-relation';
|
|
@@ -42,7 +44,6 @@ import { prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config';
|
|
|
import type { ObjectIdLike } from '../../interfaces/mongoose-utils';
|
|
import type { ObjectIdLike } from '../../interfaces/mongoose-utils';
|
|
|
import { Attachment } from '../../models';
|
|
import { Attachment } from '../../models';
|
|
|
import { PathAlreadyExistsError } from '../../models/errors';
|
|
import { PathAlreadyExistsError } from '../../models/errors';
|
|
|
-import type { IOptionsForCreate, IOptionsForUpdate } from '../../models/interfaces/page-operation';
|
|
|
|
|
import type { PageOperationDocument } from '../../models/page-operation';
|
|
import type { PageOperationDocument } from '../../models/page-operation';
|
|
|
import PageOperation from '../../models/page-operation';
|
|
import PageOperation from '../../models/page-operation';
|
|
|
import PageRedirect from '../../models/page-redirect';
|
|
import PageRedirect from '../../models/page-redirect';
|
|
@@ -420,14 +421,6 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
const subscription = await Subscription.findByUserIdAndTargetId(user._id, pageId);
|
|
const subscription = await Subscription.findByUserIdAndTargetId(user._id, pageId);
|
|
|
|
|
|
|
|
- let creatorId = page.creator;
|
|
|
|
|
- if (page.isEmpty) {
|
|
|
|
|
- // Need non-empty ancestor page to get its creatorId because empty page does NOT have it.
|
|
|
|
|
- // Use creatorId of ancestor page to determine whether the empty page is deletable
|
|
|
|
|
- const notEmptyClosestAncestor = await Page.findNonEmptyClosestAncestor(page.path);
|
|
|
|
|
- creatorId = notEmptyClosestAncestor.creator;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
const userRelatedGroups = await this.pageGrantService.getUserRelatedGroups(user);
|
|
const userRelatedGroups = await this.pageGrantService.getUserRelatedGroups(user);
|
|
|
|
|
|
|
|
const isDeletable = this.canDelete(page, user, false);
|
|
const isDeletable = this.canDelete(page, user, false);
|
|
@@ -1124,7 +1117,7 @@ class PageService implements IPageService {
|
|
|
const copyPage = { ...page };
|
|
const copyPage = { ...page };
|
|
|
|
|
|
|
|
// 3. Duplicate target
|
|
// 3. Duplicate target
|
|
|
- const options: PageCreateOptions = {
|
|
|
|
|
|
|
+ const options: IOptionsForCreate = {
|
|
|
grant,
|
|
grant,
|
|
|
grantUserGroupIds: grantedGroupIds,
|
|
grantUserGroupIds: grantedGroupIds,
|
|
|
};
|
|
};
|
|
@@ -2626,7 +2619,7 @@ class PageService implements IPageService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async normalizeParentByPath(path: string, user): Promise<void> {
|
|
async normalizeParentByPath(path: string, user): Promise<void> {
|
|
|
- const Page = mongoose.model('Page') as unknown as PageModel;
|
|
|
|
|
|
|
+ const Page = mongoose.model<PageDocument, PageModel>('Page');
|
|
|
const { PageQueryBuilder } = Page;
|
|
const { PageQueryBuilder } = Page;
|
|
|
|
|
|
|
|
// This validation is not 100% correct since it ignores user to count
|
|
// This validation is not 100% correct since it ignores user to count
|
|
@@ -2664,16 +2657,14 @@ class PageService implements IPageService {
|
|
|
if (shouldCreateNewPage) {
|
|
if (shouldCreateNewPage) {
|
|
|
const notEmptyParent = await Page.findNotEmptyParentByPathRecursively(path);
|
|
const notEmptyParent = await Page.findNotEmptyParentByPathRecursively(path);
|
|
|
|
|
|
|
|
- const options: PageCreateOptions & { grantedUsers?: ObjectIdLike[] | undefined } = {
|
|
|
|
|
- grant: notEmptyParent.grant,
|
|
|
|
|
- grantUserGroupIds: notEmptyParent.grantedGroups,
|
|
|
|
|
- grantedUsers: notEmptyParent.grantedUsers,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
systematicallyCreatedPage = await this.forceCreateBySystem(
|
|
systematicallyCreatedPage = await this.forceCreateBySystem(
|
|
|
path,
|
|
path,
|
|
|
'',
|
|
'',
|
|
|
- options,
|
|
|
|
|
|
|
+ {
|
|
|
|
|
+ grant: notEmptyParent?.grant,
|
|
|
|
|
+ grantUserIds: notEmptyParent?.grantedUsers.map(u => getIdForRef(u)),
|
|
|
|
|
+ grantUserGroupIds: notEmptyParent?.grantedGroups,
|
|
|
|
|
+ },
|
|
|
);
|
|
);
|
|
|
page = systematicallyCreatedPage;
|
|
page = systematicallyCreatedPage;
|
|
|
}
|
|
}
|
|
@@ -3680,12 +3671,12 @@ class PageService implements IPageService {
|
|
|
path: string,
|
|
path: string,
|
|
|
grantData: {
|
|
grantData: {
|
|
|
grant?: PageGrant,
|
|
grant?: PageGrant,
|
|
|
- grantedUserIds?: ObjectIdLike[],
|
|
|
|
|
|
|
+ grantUserIds?: ObjectIdLike[],
|
|
|
grantUserGroupIds?: IGrantedGroup[],
|
|
grantUserGroupIds?: IGrantedGroup[],
|
|
|
},
|
|
},
|
|
|
shouldValidateGrant: boolean,
|
|
shouldValidateGrant: boolean,
|
|
|
user?,
|
|
user?,
|
|
|
- options?: Partial<PageCreateOptions>,
|
|
|
|
|
|
|
+ options?: IOptionsForCreate,
|
|
|
): Promise<boolean> {
|
|
): Promise<boolean> {
|
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
|
|
|
|
|
@@ -3704,7 +3695,7 @@ class PageService implements IPageService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// UserGroup & Owner validation
|
|
// UserGroup & Owner validation
|
|
|
- const { grant, grantedUserIds, grantUserGroupIds } = grantData;
|
|
|
|
|
|
|
+ const { grant, grantUserIds, grantUserGroupIds } = grantData;
|
|
|
if (shouldValidateGrant) {
|
|
if (shouldValidateGrant) {
|
|
|
if (user == null) {
|
|
if (user == null) {
|
|
|
throw Error('user is required to validate grant');
|
|
throw Error('user is required to validate grant');
|
|
@@ -3716,7 +3707,7 @@ class PageService implements IPageService {
|
|
|
const isEmptyPageAlreadyExist = await Page.count({ path, isEmpty: true }) > 0;
|
|
const isEmptyPageAlreadyExist = await Page.count({ path, isEmpty: true }) > 0;
|
|
|
const shouldCheckDescendants = isEmptyPageAlreadyExist && !options?.overwriteScopesOfDescendants;
|
|
const shouldCheckDescendants = isEmptyPageAlreadyExist && !options?.overwriteScopesOfDescendants;
|
|
|
|
|
|
|
|
- isGrantNormalized = await this.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantUserGroupIds, shouldCheckDescendants);
|
|
|
|
|
|
|
+ isGrantNormalized = await this.pageGrantService.isGrantNormalized(user, path, grant, grantUserIds, grantUserGroupIds, shouldCheckDescendants);
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
|
logger.error(`Failed to validate grant of page at "${path}" of grant ${grant}:`, err);
|
|
logger.error(`Failed to validate grant of page at "${path}" of grant ${grant}:`, err);
|
|
@@ -3743,25 +3734,32 @@ class PageService implements IPageService {
|
|
|
* Create a page
|
|
* Create a page
|
|
|
* Set options.isSynchronously to true to await all process when you want to run this method multiple times at short intervals.
|
|
* Set options.isSynchronously to true to await all process when you want to run this method multiple times at short intervals.
|
|
|
*/
|
|
*/
|
|
|
- async create(path: string, body: string, user, options: IOptionsForCreate = {}): Promise<PageDocument> {
|
|
|
|
|
- const Page = mongoose.model('Page') as unknown as PageModel;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ async create(_path: string, body: string, user: HasObjectId, options: IOptionsForCreate = {}): Promise<PageDocument> {
|
|
|
// Switch method
|
|
// Switch method
|
|
|
const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
|
|
const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
|
|
|
if (!isV5Compatible) {
|
|
if (!isV5Compatible) {
|
|
|
- return this.createV4(path, body, user, options);
|
|
|
|
|
|
|
+ return this.createV4(_path, body, user, options);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Values
|
|
// Values
|
|
|
- // eslint-disable-next-line no-param-reassign
|
|
|
|
|
- path = this.crowi.xss.process(path); // sanitize path
|
|
|
|
|
- const {
|
|
|
|
|
- format = 'markdown', grantUserGroupIds,
|
|
|
|
|
- } = options;
|
|
|
|
|
- const grant = isTopPage(path) ? PageGrant.GRANT_PUBLIC : options.grant;
|
|
|
|
|
|
|
+ const path: string = this.crowi.xss.process(_path); // sanitize path
|
|
|
|
|
+
|
|
|
|
|
+ // Retrieve closest ancestor document
|
|
|
|
|
+ const Page = mongoose.model<PageDocument, PageModel>('Page');
|
|
|
|
|
+ const closestAncestor = await Page.findNonEmptyClosestAncestor(path);
|
|
|
|
|
+
|
|
|
|
|
+ // Determine grantData
|
|
|
|
|
+ const grant = options.grant ?? closestAncestor?.grant ?? PageGrant.GRANT_PUBLIC;
|
|
|
|
|
+ const grantedUserIds = grant === PageGrant.GRANT_OWNER ? [user._id] : undefined;
|
|
|
|
|
+ const grantUserGroupIds = options.grantUserGroupIds
|
|
|
|
|
+ ?? (
|
|
|
|
|
+ closestAncestor != null
|
|
|
|
|
+ ? await this.pageGrantService.getUserRelatedGrantedGroups(closestAncestor, user)
|
|
|
|
|
+ : undefined
|
|
|
|
|
+ );
|
|
|
const grantData = {
|
|
const grantData = {
|
|
|
grant,
|
|
grant,
|
|
|
- grantedUserIds: grant === PageGrant.GRANT_OWNER ? [user._id] : undefined,
|
|
|
|
|
|
|
+ grantedUserIds,
|
|
|
grantUserGroupIds,
|
|
grantUserGroupIds,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -3797,7 +3795,7 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
// Create revision
|
|
// Create revision
|
|
|
const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
|
- const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
|
|
|
|
|
|
|
+ const newRevision = Revision.prepareRevision(savedPage, body, null, user);
|
|
|
savedPage = await pushRevision(savedPage, newRevision, user);
|
|
savedPage = await pushRevision(savedPage, newRevision, user);
|
|
|
await savedPage.populateDataToShowRevision();
|
|
await savedPage.populateDataToShowRevision();
|
|
|
|
|
|
|
@@ -3821,12 +3819,7 @@ class PageService implements IPageService {
|
|
|
throw err;
|
|
throw err;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (options.isSynchronously) {
|
|
|
|
|
- await this.createSubOperation(savedPage, user, options, pageOp._id);
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- this.createSubOperation(savedPage, user, options, pageOp._id);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.createSubOperation(savedPage, user, options, pageOp._id);
|
|
|
|
|
|
|
|
return savedPage;
|
|
return savedPage;
|
|
|
}
|
|
}
|
|
@@ -3912,7 +3905,7 @@ class PageService implements IPageService {
|
|
|
path: string,
|
|
path: string,
|
|
|
grantData: {
|
|
grantData: {
|
|
|
grant: PageGrant,
|
|
grant: PageGrant,
|
|
|
- grantedUserIds?: ObjectIdLike[],
|
|
|
|
|
|
|
+ grantUserIds?: ObjectIdLike[],
|
|
|
grantUserGroupId?: ObjectIdLike,
|
|
grantUserGroupId?: ObjectIdLike,
|
|
|
},
|
|
},
|
|
|
): Promise<boolean> {
|
|
): Promise<boolean> {
|
|
@@ -3921,13 +3914,13 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @private
|
|
* @private
|
|
|
- * This method receives the same arguments as the PageService.create method does except for the added type '{ grantedUsers?: ObjectIdLike[] }'.
|
|
|
|
|
|
|
+ * This method receives the same arguments as the PageService.create method does except for the added type '{ grantUserIds?: ObjectIdLike[] }'.
|
|
|
* This additional value is used to determine the grantedUser of the page to be created by system.
|
|
* This additional value is used to determine the grantedUser of the page to be created by system.
|
|
|
* This method must not run isGrantNormalized method to validate grant. **If necessary, run it before use this method.**
|
|
* This method must not run isGrantNormalized method to validate grant. **If necessary, run it before use this method.**
|
|
|
* -- Reason 1: This is because it is not expected to use this method when the grant validation is required.
|
|
* -- Reason 1: This is because it is not expected to use this method when the grant validation is required.
|
|
|
* -- Reason 2: This is because it is not expected to use this method when the program cannot determine the operator.
|
|
* -- Reason 2: This is because it is not expected to use this method when the program cannot determine the operator.
|
|
|
*/
|
|
*/
|
|
|
- private async forceCreateBySystem(path: string, body: string, options: PageCreateOptions & { grantedUsers?: ObjectIdLike[] }): Promise<PageDocument> {
|
|
|
|
|
|
|
+ async forceCreateBySystem(path: string, body: string, options: IOptionsForCreate & { grantUserIds?: ObjectIdLike[] }): Promise<PageDocument> {
|
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
|
|
|
|
|
|
const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
|
|
const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
|
|
@@ -3940,7 +3933,7 @@ class PageService implements IPageService {
|
|
|
path = this.crowi.xss.process(path); // sanitize path
|
|
path = this.crowi.xss.process(path); // sanitize path
|
|
|
|
|
|
|
|
const {
|
|
const {
|
|
|
- format = 'markdown', grantUserGroupIds, grantedUsers,
|
|
|
|
|
|
|
+ grantUserGroupIds, grantUserIds,
|
|
|
} = options;
|
|
} = options;
|
|
|
const grant = isTopPage(path) ? Page.GRANT_PUBLIC : options.grant;
|
|
const grant = isTopPage(path) ? Page.GRANT_PUBLIC : options.grant;
|
|
|
|
|
|
|
@@ -3949,12 +3942,12 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
const grantData = {
|
|
const grantData = {
|
|
|
grant,
|
|
grant,
|
|
|
- grantedUserIds: isGrantOwner ? grantedUsers : undefined,
|
|
|
|
|
|
|
+ grantUserIds: isGrantOwner ? grantUserIds : undefined,
|
|
|
grantUserGroupIds,
|
|
grantUserGroupIds,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Validate
|
|
// Validate
|
|
|
- if (isGrantOwner && grantedUsers?.length !== 1) {
|
|
|
|
|
|
|
+ if (isGrantOwner && grantUserIds?.length !== 1) {
|
|
|
throw Error('grantedUser must exist when grant is GRANT_OWNER');
|
|
throw Error('grantedUser must exist when grant is GRANT_OWNER');
|
|
|
}
|
|
}
|
|
|
const canProcessForceCreateBySystem = await this.canProcessForceCreateBySystem(path, grantData);
|
|
const canProcessForceCreateBySystem = await this.canProcessForceCreateBySystem(path, grantData);
|
|
@@ -3970,7 +3963,7 @@ class PageService implements IPageService {
|
|
|
this.setFieldExceptForGrantRevisionParent(page, path);
|
|
this.setFieldExceptForGrantRevisionParent(page, path);
|
|
|
|
|
|
|
|
// Apply scope
|
|
// Apply scope
|
|
|
- page.applyScope({ _id: grantedUsers?.[0] }, grant, grantUserGroupIds);
|
|
|
|
|
|
|
+ page.applyScope({ _id: grantUserIds?.[0] }, grant, grantUserGroupIds);
|
|
|
|
|
|
|
|
// Set parent
|
|
// Set parent
|
|
|
if (isTopPage(path) || isGrantRestricted) { // set parent to null when GRANT_RESTRICTED
|
|
if (isTopPage(path) || isGrantRestricted) { // set parent to null when GRANT_RESTRICTED
|
|
@@ -3987,7 +3980,7 @@ class PageService implements IPageService {
|
|
|
// Create revision
|
|
// Create revision
|
|
|
const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
|
const dummyUser = { _id: new mongoose.Types.ObjectId() };
|
|
const dummyUser = { _id: new mongoose.Types.ObjectId() };
|
|
|
- const newRevision = Revision.prepareRevision(savedPage, body, null, dummyUser, { format });
|
|
|
|
|
|
|
+ const newRevision = Revision.prepareRevision(savedPage, body, null, dummyUser);
|
|
|
savedPage = await pushRevision(savedPage, newRevision, dummyUser);
|
|
savedPage = await pushRevision(savedPage, newRevision, dummyUser);
|
|
|
|
|
|
|
|
// Update descendantCount
|
|
// Update descendantCount
|
|
@@ -4016,7 +4009,6 @@ class PageService implements IPageService {
|
|
|
const options: IOptionsForUpdate = {
|
|
const options: IOptionsForUpdate = {
|
|
|
grant,
|
|
grant,
|
|
|
userRelatedGrantUserGroupIds: userRelatedGrantedGroups,
|
|
userRelatedGrantUserGroupIds: userRelatedGrantedGroups,
|
|
|
- isSyncRevisionToHackmd: false,
|
|
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
return this.updatePage(page, null, null, user, options);
|
|
return this.updatePage(page, null, null, user, options);
|
|
@@ -4091,7 +4083,7 @@ class PageService implements IPageService {
|
|
|
pageData: PageDocument,
|
|
pageData: PageDocument,
|
|
|
body: string | null,
|
|
body: string | null,
|
|
|
previousBody: string | null,
|
|
previousBody: string | null,
|
|
|
- user,
|
|
|
|
|
|
|
+ user: IUserHasId,
|
|
|
options: IOptionsForUpdate = {},
|
|
options: IOptionsForUpdate = {},
|
|
|
): Promise<PageDocument> {
|
|
): Promise<PageDocument> {
|
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
@@ -4230,7 +4222,6 @@ class PageService implements IPageService {
|
|
|
const grantUserGroupIds = options.userRelatedGrantUserGroupIds != null
|
|
const grantUserGroupIds = options.userRelatedGrantUserGroupIds != null
|
|
|
? (await this.getNewGrantedGroups(options.userRelatedGrantUserGroupIds, pageData, user))
|
|
? (await this.getNewGrantedGroups(options.userRelatedGrantUserGroupIds, pageData, user))
|
|
|
: pageData.grantedGroups;
|
|
: pageData.grantedGroups;
|
|
|
- const isSyncRevisionToHackmd = options.isSyncRevisionToHackmd;
|
|
|
|
|
|
|
|
|
|
// validate multiple group grant before save using pageData and options
|
|
// validate multiple group grant before save using pageData and options
|
|
|
await this.pageGrantService.validateGrantChange(user, pageData.grantedGroups, grant, grantUserGroupIds);
|
|
await this.pageGrantService.validateGrantChange(user, pageData.grantedGroups, grant, grantUserGroupIds);
|