|
@@ -10,10 +10,11 @@ import nodePath from 'path';
|
|
|
import { getOrCreateModel } from '@growi/core';
|
|
import { getOrCreateModel } from '@growi/core';
|
|
|
import loggerFactory from '../../utils/logger';
|
|
import loggerFactory from '../../utils/logger';
|
|
|
import Crowi from '../crowi';
|
|
import Crowi from '../crowi';
|
|
|
|
|
+import { IPage } from './interfaces/page';
|
|
|
|
|
+import { getPageSchema, PageQueryBuilder } from './obsolete-page';
|
|
|
|
|
|
|
|
const logger = loggerFactory('growi:models:page');
|
|
const logger = loggerFactory('growi:models:page');
|
|
|
|
|
|
|
|
-const getPageSchema = require('./obsolete-page');
|
|
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
|
* define schema
|
|
* define schema
|
|
@@ -27,31 +28,6 @@ const PAGE_GRANT_ERROR = 1;
|
|
|
const STATUS_PUBLISHED = 'published';
|
|
const STATUS_PUBLISHED = 'published';
|
|
|
const STATUS_DELETED = 'deleted';
|
|
const STATUS_DELETED = 'deleted';
|
|
|
|
|
|
|
|
-export interface IPage {
|
|
|
|
|
- parent: Schema.Types.ObjectId,
|
|
|
|
|
- isEmpty: boolean,
|
|
|
|
|
- path: string
|
|
|
|
|
- revision: Schema.Types.ObjectId,
|
|
|
|
|
- redirectTo: string,
|
|
|
|
|
- status: string,
|
|
|
|
|
- grant: number,
|
|
|
|
|
- grantedUsers: Schema.Types.ObjectId[],
|
|
|
|
|
- grantedGroup: Schema.Types.ObjectId,
|
|
|
|
|
- creator: Schema.Types.ObjectId,
|
|
|
|
|
- lastUpdateUser: Schema.Types.ObjectId,
|
|
|
|
|
- liker: Schema.Types.ObjectId[],
|
|
|
|
|
- seenUsers: Schema.Types.ObjectId[],
|
|
|
|
|
- commentCount: number,
|
|
|
|
|
- slackChannels: string,
|
|
|
|
|
- pageIdOnHackmd: string,
|
|
|
|
|
- revisionHackmdSynced: Schema.Types.ObjectId,
|
|
|
|
|
- hasDraftOnHackmd: boolean,
|
|
|
|
|
- createdAt: Date,
|
|
|
|
|
- updatedAt: Date,
|
|
|
|
|
- deleteUser: Schema.Types.ObjectId,
|
|
|
|
|
- deletedAt: Date,
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
export interface PageDocument extends IPage, Document {}
|
|
export interface PageDocument extends IPage, Document {}
|
|
|
|
|
|
|
|
export interface PageModel extends Model<PageDocument> {
|
|
export interface PageModel extends Model<PageDocument> {
|
|
@@ -96,94 +72,95 @@ const schema = new Schema<PageDocument, PageModel>({
|
|
|
schema.plugin(mongoosePaginate);
|
|
schema.plugin(mongoosePaginate);
|
|
|
schema.plugin(uniqueValidator);
|
|
schema.plugin(uniqueValidator);
|
|
|
|
|
|
|
|
-export default (crowi: Crowi): any => {
|
|
|
|
|
- // add old page schema methods
|
|
|
|
|
- const pageSchema = getPageSchema(crowi);
|
|
|
|
|
- schema.methods = pageSchema.methods;
|
|
|
|
|
- schema.statics = pageSchema.statics;
|
|
|
|
|
- const { PageQueryBuilder } = pageSchema.statics;
|
|
|
|
|
|
|
|
|
|
- const collectAncestorPaths = (path: string, ancestorPaths: string[] = []): string[] => {
|
|
|
|
|
- const parentPath = nodePath.dirname(path);
|
|
|
|
|
- ancestorPaths.push(parentPath);
|
|
|
|
|
-
|
|
|
|
|
- if (path !== '/') return collectAncestorPaths(parentPath, ancestorPaths);
|
|
|
|
|
-
|
|
|
|
|
- return ancestorPaths;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- schema.statics.createEmptyPagesByPaths = async function(paths: string[]): Promise<void> {
|
|
|
|
|
- const Page = this;
|
|
|
|
|
-
|
|
|
|
|
- // find existing parents
|
|
|
|
|
- const builder = new PageQueryBuilder(Page.find({}, { _id: 0, path: 1 }));
|
|
|
|
|
- const existingPages = await builder
|
|
|
|
|
- .addConditionToListByPathsArray(paths)
|
|
|
|
|
- .query
|
|
|
|
|
- .lean()
|
|
|
|
|
- .exec();
|
|
|
|
|
- const existingPagePaths = existingPages.map(page => page.path);
|
|
|
|
|
-
|
|
|
|
|
- // paths to create empty pages
|
|
|
|
|
- const notExistingPagePaths = paths.filter(path => !existingPagePaths.includes(path));
|
|
|
|
|
-
|
|
|
|
|
- // insertMany empty pages
|
|
|
|
|
- try {
|
|
|
|
|
- await Page.insertMany(notExistingPagePaths.map(path => ({ path, isEmpty: true })));
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- logger.error('Failed to insert empty pages.', err);
|
|
|
|
|
- throw err;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- schema.statics.getParentIdAndFillAncestors = async function(path: string): Promise<string | null> {
|
|
|
|
|
- const Page = this;
|
|
|
|
|
- const parentPath = nodePath.dirname(path);
|
|
|
|
|
|
|
+/*
|
|
|
|
|
+ * Methods
|
|
|
|
|
+ */
|
|
|
|
|
+const collectAncestorPaths = (path: string, ancestorPaths: string[] = []): string[] => {
|
|
|
|
|
+ const parentPath = nodePath.dirname(path);
|
|
|
|
|
+ ancestorPaths.push(parentPath);
|
|
|
|
|
+
|
|
|
|
|
+ if (path !== '/') return collectAncestorPaths(parentPath, ancestorPaths);
|
|
|
|
|
+
|
|
|
|
|
+ return ancestorPaths;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+schema.statics.createEmptyPagesByPaths = async function(paths: string[]): Promise<void> {
|
|
|
|
|
+ // find existing parents
|
|
|
|
|
+ const builder = new PageQueryBuilder(this.find({}, { _id: 0, path: 1 }));
|
|
|
|
|
+ const existingPages = await builder
|
|
|
|
|
+ .addConditionToListByPathsArray(paths)
|
|
|
|
|
+ .query
|
|
|
|
|
+ .lean()
|
|
|
|
|
+ .exec();
|
|
|
|
|
+ const existingPagePaths = existingPages.map(page => page.path);
|
|
|
|
|
+
|
|
|
|
|
+ // paths to create empty pages
|
|
|
|
|
+ const notExistingPagePaths = paths.filter(path => !existingPagePaths.includes(path));
|
|
|
|
|
+
|
|
|
|
|
+ // insertMany empty pages
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.insertMany(notExistingPagePaths.map(path => ({ path, isEmpty: true })));
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (err) {
|
|
|
|
|
+ logger.error('Failed to insert empty pages.', err);
|
|
|
|
|
+ throw err;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+schema.statics.getParentIdAndFillAncestors = async function(path: string): Promise<string | null> {
|
|
|
|
|
+ const parentPath = nodePath.dirname(path);
|
|
|
|
|
|
|
|
- const parent = await Page.findOne({ path: parentPath }); // find the oldest parent which must always be the true parent
|
|
|
|
|
- if (parent != null) { // fill parents if parent is null
|
|
|
|
|
- return parent._id;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const ancestorPaths = collectAncestorPaths(path); // paths of parents need to be created
|
|
|
|
|
-
|
|
|
|
|
- // just create ancestors with empty pages
|
|
|
|
|
- await Page.createEmptyPagesByPaths(ancestorPaths);
|
|
|
|
|
-
|
|
|
|
|
- // find ancestors
|
|
|
|
|
- const builder = new PageQueryBuilder(Page.find({}, { _id: 1, path: 1 }));
|
|
|
|
|
- const ancestors = await builder
|
|
|
|
|
- .addConditionToListByPathsArray(ancestorPaths)
|
|
|
|
|
- .query
|
|
|
|
|
- .lean()
|
|
|
|
|
- .exec();
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- const ancestorsMap = new Map(); // Map<path, _id>
|
|
|
|
|
- ancestors.forEach(page => ancestorsMap.set(page.path, page._id));
|
|
|
|
|
-
|
|
|
|
|
- // bulkWrite to update ancestors
|
|
|
|
|
- const nonRootAncestors = ancestors.filter(page => page.path !== '/');
|
|
|
|
|
- const operations = nonRootAncestors.map((page) => {
|
|
|
|
|
- const { path } = page;
|
|
|
|
|
- const parentPath = nodePath.dirname(path);
|
|
|
|
|
- return {
|
|
|
|
|
- updateOne: {
|
|
|
|
|
- filter: {
|
|
|
|
|
- path,
|
|
|
|
|
- },
|
|
|
|
|
- update: {
|
|
|
|
|
- parent: ancestorsMap.get(parentPath),
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ const parent = await this.findOne({ path: parentPath }); // find the oldest parent which must always be the true parent
|
|
|
|
|
+ if (parent != null) { // fill parents if parent is null
|
|
|
|
|
+ return parent._id;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const ancestorPaths = collectAncestorPaths(path); // paths of parents need to be created
|
|
|
|
|
+
|
|
|
|
|
+ // just create ancestors with empty pages
|
|
|
|
|
+ await this.createEmptyPagesByPaths(ancestorPaths);
|
|
|
|
|
+
|
|
|
|
|
+ // find ancestors
|
|
|
|
|
+ const builder = new PageQueryBuilder(this.find({}, { _id: 1, path: 1 }));
|
|
|
|
|
+ const ancestors = await builder
|
|
|
|
|
+ .addConditionToListByPathsArray(ancestorPaths)
|
|
|
|
|
+ .query
|
|
|
|
|
+ .lean()
|
|
|
|
|
+ .exec();
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ const ancestorsMap = new Map(); // Map<path, _id>
|
|
|
|
|
+ ancestors.forEach(page => ancestorsMap.set(page.path, page._id));
|
|
|
|
|
+
|
|
|
|
|
+ // bulkWrite to update ancestors
|
|
|
|
|
+ const nonRootAncestors = ancestors.filter(page => page.path !== '/');
|
|
|
|
|
+ const operations = nonRootAncestors.map((page) => {
|
|
|
|
|
+ const { path } = page;
|
|
|
|
|
+ const parentPath = nodePath.dirname(path);
|
|
|
|
|
+ return {
|
|
|
|
|
+ updateOne: {
|
|
|
|
|
+ filter: {
|
|
|
|
|
+ path,
|
|
|
},
|
|
},
|
|
|
- };
|
|
|
|
|
- });
|
|
|
|
|
- await Page.bulkWrite(operations);
|
|
|
|
|
|
|
+ update: {
|
|
|
|
|
+ parent: ancestorsMap.get(parentPath),
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ await this.bulkWrite(operations);
|
|
|
|
|
+
|
|
|
|
|
+ const parentId = ancestorsMap.get(parentPath);
|
|
|
|
|
+ return parentId;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default (crowi: Crowi): any => {
|
|
|
|
|
+ // add old page schema methods
|
|
|
|
|
+ const pageSchema = getPageSchema(crowi);
|
|
|
|
|
+ schema.methods = { ...pageSchema.methods, ...schema.methods };
|
|
|
|
|
+ schema.statics = { ...pageSchema.statics, ...schema.statics };
|
|
|
|
|
|
|
|
- const parentId = ancestorsMap.get(parentPath);
|
|
|
|
|
- return parentId;
|
|
|
|
|
- };
|
|
|
|
|
|
|
|
|
|
return getOrCreateModel<PageDocument, PageModel>('Page', schema);
|
|
return getOrCreateModel<PageDocument, PageModel>('Page', schema);
|
|
|
};
|
|
};
|