summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authoranatawa12 <anatawa12@icloud.com>2025-08-15 22:39:55 +0900
committerGitHub <noreply@github.com>2025-08-15 22:39:55 +0900
commit60f7278aff27b9a0e03c1f1a2a77663cfb0e0ddb (patch)
tree76d9f4e99144879566c5d39da7de7bd7f11a7668 /packages/backend/src
parentenhance(frontend): improve enableInfiniteScroll stability (diff)
downloadmisskey-60f7278aff27b9a0e03c1f1a2a77663cfb0e0ddb.tar.gz
misskey-60f7278aff27b9a0e03c1f1a2a77663cfb0e0ddb.tar.bz2
misskey-60f7278aff27b9a0e03c1f1a2a77663cfb0e0ddb.zip
fix: Remote Note Cleaning will delete notes embedded in a page (#16408)
* feat: preserve number of pages referencing the note * chore: delete pages on account delete * fix: notes on the pages are removed by CleanRemoteNotes * test: add the simplest test for page embedded notes * fix: section block is not considered * fix: section block is not considered in migration * chore: remove comments from columns * revert unnecessary change * add pageCount to webhook test * fix type error on backend
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/core/CoreModule.ts6
-rw-r--r--packages/backend/src/core/PageService.ts223
-rw-r--r--packages/backend/src/core/WebhookTestService.ts1
-rw-r--r--packages/backend/src/models/Note.ts7
-rw-r--r--packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts1
-rw-r--r--packages/backend/src/queue/processors/DeleteAccountProcessorService.ts29
-rw-r--r--packages/backend/src/server/api/endpoints/pages/create.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/pages/delete.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/pages/update.ts69
9 files changed, 322 insertions, 96 deletions
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 0c0c5d3a39..a30bff0fe4 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -78,6 +78,7 @@ import { ChannelFollowingService } from './ChannelFollowingService.js';
import { ChatService } from './ChatService.js';
import { RegistryApiService } from './RegistryApiService.js';
import { ReversiService } from './ReversiService.js';
+import { PageService } from './PageService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
import FederationChart from './chart/charts/federation.js';
@@ -227,6 +228,7 @@ const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService',
const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService };
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
+const $PageService: Provider = { provide: 'PageService', useExisting: PageService };
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -379,6 +381,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ChatService,
RegistryApiService,
ReversiService,
+ PageService,
ChartLoggerService,
FederationChart,
@@ -527,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ChatService,
$RegistryApiService,
$ReversiService,
+ $PageService,
$ChartLoggerService,
$FederationChart,
@@ -676,6 +680,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ChatService,
RegistryApiService,
ReversiService,
+ PageService,
FederationChart,
NotesChart,
@@ -822,6 +827,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ChatService,
$RegistryApiService,
$ReversiService,
+ $PageService,
$FederationChart,
$NotesChart,
diff --git a/packages/backend/src/core/PageService.ts b/packages/backend/src/core/PageService.ts
new file mode 100644
index 0000000000..7f0e5c7ccc
--- /dev/null
+++ b/packages/backend/src/core/PageService.ts
@@ -0,0 +1,223 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DataSource, In, Not } from 'typeorm';
+import { DI } from '@/di-symbols.js';
+import {
+ type NotesRepository,
+ MiPage,
+ type PagesRepository,
+ MiDriveFile,
+ type UsersRepository,
+ MiNote,
+} from '@/models/_.js';
+import { bindThis } from '@/decorators.js';
+import { RoleService } from '@/core/RoleService.js';
+import { IdService } from '@/core/IdService.js';
+import type { MiUser } from '@/models/User.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+
+export interface PageBody {
+ title: string;
+ name: string;
+ summary: string | null;
+ content: Array<Record<string, any>>;
+ variables: Array<Record<string, any>>;
+ script: string;
+ eyeCatchingImage?: MiDriveFile | null;
+ font: string;
+ alignCenter: boolean;
+ hideTitleWhenPinned: boolean;
+}
+
+@Injectable()
+export class PageService {
+ constructor(
+ @Inject(DI.db)
+ private db: DataSource,
+
+ @Inject(DI.pagesRepository)
+ private pagesRepository: PagesRepository,
+
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ private roleService: RoleService,
+ private moderationLogService: ModerationLogService,
+ private idService: IdService,
+ ) {
+ }
+
+ @bindThis
+ public async create(
+ me: MiUser,
+ body: PageBody,
+ ): Promise<MiPage> {
+ await this.pagesRepository.findBy({
+ userId: me.id,
+ name: body.name,
+ }).then(result => {
+ if (result.length > 0) {
+ throw new IdentifiableError('1a79e38e-3d83-4423-845b-a9d83ff93b61');
+ }
+ });
+
+ const page = await this.pagesRepository.insertOne(new MiPage({
+ id: this.idService.gen(),
+ updatedAt: new Date(),
+ title: body.title,
+ name: body.name,
+ summary: body.summary,
+ content: body.content,
+ variables: body.variables,
+ script: body.script,
+ eyeCatchingImageId: body.eyeCatchingImage ? body.eyeCatchingImage.id : null,
+ userId: me.id,
+ visibility: 'public',
+ alignCenter: body.alignCenter,
+ hideTitleWhenPinned: body.hideTitleWhenPinned,
+ font: body.font,
+ }));
+
+ const referencedNotes = this.collectReferencedNotes(page.content);
+ if (referencedNotes.length > 0) {
+ await this.notesRepository.increment({ id: In(referencedNotes) }, 'pageCount', 1);
+ }
+
+ return page;
+ }
+
+ @bindThis
+ public async update(
+ me: MiUser,
+ pageId: MiPage['id'],
+ body: Partial<PageBody>,
+ ): Promise<void> {
+ await this.db.transaction(async (transaction) => {
+ const page = await transaction.findOne(MiPage, {
+ where: {
+ id: pageId,
+ },
+ lock: { mode: 'for_no_key_update' },
+ });
+
+ if (page == null) {
+ throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f');
+ }
+ if (page.userId !== me.id) {
+ throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616');
+ }
+
+ if (body.name != null) {
+ await transaction.findBy(MiPage, {
+ id: Not(pageId),
+ userId: me.id,
+ name: body.name,
+ }).then(result => {
+ if (result.length > 0) {
+ throw new IdentifiableError('d05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4');
+ }
+ });
+ }
+
+ await transaction.update(MiPage, page.id, {
+ updatedAt: new Date(),
+ title: body.title,
+ name: body.name,
+ summary: body.summary === undefined ? page.summary : body.summary,
+ content: body.content,
+ variables: body.variables,
+ script: body.script,
+ alignCenter: body.alignCenter,
+ hideTitleWhenPinned: body.hideTitleWhenPinned,
+ font: body.font,
+ eyeCatchingImageId: body.eyeCatchingImage === undefined ? undefined : (body.eyeCatchingImage?.id ?? null),
+ });
+
+ console.log("page.content", page.content);
+
+ if (body.content != null) {
+ const beforeReferencedNotes = this.collectReferencedNotes(page.content);
+ const afterReferencedNotes = this.collectReferencedNotes(body.content);
+
+ const removedNotes = beforeReferencedNotes.filter(noteId => !afterReferencedNotes.includes(noteId));
+ const addedNotes = afterReferencedNotes.filter(noteId => !beforeReferencedNotes.includes(noteId));
+
+ if (removedNotes.length > 0) {
+ await transaction.decrement(MiNote, { id: In(removedNotes) }, 'pageCount', 1);
+ }
+ if (addedNotes.length > 0) {
+ await transaction.increment(MiNote, { id: In(addedNotes) }, 'pageCount', 1);
+ }
+ }
+ });
+ }
+
+ @bindThis
+ public async delete(me: MiUser, pageId: MiPage['id']): Promise<void> {
+ await this.db.transaction(async (transaction) => {
+ const page = await transaction.findOne(MiPage, {
+ where: {
+ id: pageId,
+ },
+ lock: { mode: 'pessimistic_write' }, // same lock level as DELETE
+ });
+
+ if (page == null) {
+ throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f');
+ }
+
+ if (!await this.roleService.isModerator(me) && page.userId !== me.id) {
+ throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616');
+ }
+
+ await transaction.delete(MiPage, page.id);
+
+ if (page.userId !== me.id) {
+ const user = await this.usersRepository.findOneByOrFail({ id: page.userId });
+ this.moderationLogService.log(me, 'deletePage', {
+ pageId: page.id,
+ pageUserId: page.userId,
+ pageUserUsername: user.username,
+ page,
+ });
+ }
+
+ const referencedNotes = this.collectReferencedNotes(page.content);
+ if (referencedNotes.length > 0) {
+ await transaction.decrement(MiNote, { id: In(referencedNotes) }, 'pageCount', 1);
+ }
+ });
+ }
+
+ collectReferencedNotes(content: MiPage['content']): string[] {
+ const referencingNotes = new Set<string>();
+ const recursiveCollect = (content: unknown[]) => {
+ for (const contentElement of content) {
+ if (typeof contentElement === 'object'
+ && contentElement !== null
+ && 'type' in contentElement) {
+ if (contentElement.type === 'note'
+ && 'note' in contentElement
+ && typeof contentElement.note === 'string') {
+ referencingNotes.add(contentElement.note);
+ }
+ if (contentElement.type === 'section'
+ && 'children' in contentElement
+ && Array.isArray(contentElement.children)) {
+ recursiveCollect(contentElement.children);
+ }
+ }
+ }
+ };
+ recursiveCollect(content);
+ return [...referencingNotes];
+ }
+}
diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts
index 9cf985b688..907b5ea6be 100644
--- a/packages/backend/src/core/WebhookTestService.ts
+++ b/packages/backend/src/core/WebhookTestService.ts
@@ -85,6 +85,7 @@ function generateDummyNote(override?: Partial<MiNote>): MiNote {
renoteCount: 10,
repliesCount: 5,
clippedCount: 0,
+ pageCount: 0,
reactions: {},
visibility: 'public',
uri: null,
diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts
index ff46615729..26d5c1d535 100644
--- a/packages/backend/src/models/Note.ts
+++ b/packages/backend/src/models/Note.ts
@@ -114,6 +114,13 @@ export class MiNote {
})
public clippedCount: number;
+ // The number of note page blocks referencing this note.
+ // This column is used by Remote Note Cleaning and manually updated rather than automatically with triggers.
+ @Column('smallint', {
+ default: 0,
+ })
+ public pageCount: number;
+
@Column('jsonb', {
default: {},
})
diff --git a/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts
index 77a9dc5557..f53d403280 100644
--- a/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts
@@ -82,6 +82,7 @@ export class CleanRemoteNotesProcessorService {
const removalCriteria = [
'note."id" < :newestLimit',
'note."clippedCount" = 0',
+ 'note."pageCount" = 0',
'note."userHost" IS NOT NULL',
'NOT EXISTS (SELECT 1 FROM user_note_pining WHERE "noteId" = note."id")',
'NOT EXISTS (SELECT 1 FROM note_favorite WHERE "noteId" = note."id")',
diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
index 14a53e0c42..b643c2a6d0 100644
--- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
@@ -6,7 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { MoreThan } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { DriveFilesRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
@@ -14,6 +14,7 @@ import type { MiNote } from '@/models/Note.js';
import { EmailService } from '@/core/EmailService.js';
import { bindThis } from '@/decorators.js';
import { SearchService } from '@/core/SearchService.js';
+import { PageService } from '@/core/PageService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbUserDeleteJobData } from '../types.js';
@@ -35,7 +36,11 @@ export class DeleteAccountProcessorService {
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
+ @Inject(DI.pagesRepository)
+ private pagesRepository: PagesRepository,
+
private driveService: DriveService,
+ private pageService: PageService,
private emailService: EmailService,
private queueLoggerService: QueueLoggerService,
private searchService: SearchService,
@@ -112,6 +117,28 @@ export class DeleteAccountProcessorService {
this.logger.succ('All of files deleted');
}
+ {
+ // delete pages. Necessary for decrementing pageCount of notes.
+ while (true) {
+ const pages = await this.pagesRepository.find({
+ where: {
+ userId: user.id,
+ },
+ take: 100,
+ order: {
+ id: 1,
+ },
+ });
+
+ if (pages.length === 0) {
+ break;
+ }
+ for (const page of pages) {
+ await this.pageService.delete(user, page.id);
+ }
+ }
+ }
+
{ // Send email notification
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
if (profile.email && profile.emailVerified) {
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 6de5fe3d44..96bc2a953a 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -5,12 +5,13 @@
import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
-import type { DriveFilesRepository, PagesRepository } from '@/models/_.js';
-import { IdService } from '@/core/IdService.js';
-import { MiPage, pageNameSchema } from '@/models/Page.js';
+import type { DriveFilesRepository, MiDriveFile, PagesRepository } from '@/models/_.js';
+import { pageNameSchema } from '@/models/Page.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { DI } from '@/di-symbols.js';
+import { PageService } from '@/core/PageService.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -77,11 +78,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
+ private pageService: PageService,
private pageEntityService: PageEntityService,
- private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
- let eyeCatchingImage = null;
+ let eyeCatchingImage: MiDriveFile | null = null;
if (ps.eyeCatchingImageId != null) {
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
id: ps.eyeCatchingImageId,
@@ -102,24 +103,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
});
- const page = await this.pagesRepository.insertOne(new MiPage({
- id: this.idService.gen(),
- updatedAt: new Date(),
- title: ps.title,
- name: ps.name,
- summary: ps.summary,
- content: ps.content,
- variables: ps.variables,
- script: ps.script,
- eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
- userId: me.id,
- visibility: 'public',
- alignCenter: ps.alignCenter,
- hideTitleWhenPinned: ps.hideTitleWhenPinned,
- font: ps.font,
- }));
+ try {
+ const page = await this.pageService.create(me, {
+ ...ps,
+ eyeCatchingImage,
+ summary: ps.summary ?? null,
+ });
- return await this.pageEntityService.pack(page);
+ return await this.pageEntityService.pack(page);
+ } catch (err) {
+ if (err instanceof IdentifiableError && err.id === '1a79e38e-3d83-4423-845b-a9d83ff93b61') {
+ throw new ApiError(meta.errors.nameAlreadyExists);
+ }
+ throw err;
+ }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts
index f2bc946788..a33868552d 100644
--- a/packages/backend/src/server/api/endpoints/pages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/pages/delete.ts
@@ -4,12 +4,14 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { PagesRepository, UsersRepository } from '@/models/_.js';
+import type { MiDriveFile, PagesRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { PageService } from '@/core/PageService.js';
export const meta = {
tags: ['pages'],
@@ -44,36 +46,17 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.pagesRepository)
- private pagesRepository: PagesRepository,
-
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
- private moderationLogService: ModerationLogService,
- private roleService: RoleService,
+ private pageService: PageService,
) {
super(meta, paramDef, async (ps, me) => {
- const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
-
- if (page == null) {
- throw new ApiError(meta.errors.noSuchPage);
- }
-
- if (!await this.roleService.isModerator(me) && page.userId !== me.id) {
- throw new ApiError(meta.errors.accessDenied);
- }
-
- await this.pagesRepository.delete(page.id);
-
- if (page.userId !== me.id) {
- const user = await this.usersRepository.findOneByOrFail({ id: page.userId });
- this.moderationLogService.log(me, 'deletePage', {
- pageId: page.id,
- pageUserId: page.userId,
- pageUserUsername: user.username,
- page,
- });
+ try {
+ await this.pageService.delete(me, ps.pageId);
+ } catch (err) {
+ if (err instanceof IdentifiableError) {
+ if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage);
+ if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied);
+ }
+ throw err;
}
});
}
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index a6aeb6002e..6fa5c1d75c 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -4,13 +4,14 @@
*/
import ms from 'ms';
-import { Not } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
-import type { PagesRepository, DriveFilesRepository } from '@/models/_.js';
+import type { DriveFilesRepository, MiDriveFile } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { pageNameSchema } from '@/models/Page.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { PageService } from '@/core/PageService.js';
export const meta = {
tags: ['pages'],
@@ -75,57 +76,37 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.pagesRepository)
- private pagesRepository: PagesRepository,
-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
+
+ private pageService: PageService,
) {
super(meta, paramDef, async (ps, me) => {
- const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
- if (page == null) {
- throw new ApiError(meta.errors.noSuchPage);
- }
- if (page.userId !== me.id) {
- throw new ApiError(meta.errors.accessDenied);
- }
-
- if (ps.eyeCatchingImageId != null) {
- const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
- id: ps.eyeCatchingImageId,
- userId: me.id,
- });
+ try {
+ let eyeCatchingImage: MiDriveFile | null | undefined | string = ps.eyeCatchingImageId;
+ if (eyeCatchingImage != null) {
+ eyeCatchingImage = await this.driveFilesRepository.findOneBy({
+ id: eyeCatchingImage,
+ userId: me.id,
+ });
- if (eyeCatchingImage == null) {
- throw new ApiError(meta.errors.noSuchFile);
+ if (eyeCatchingImage == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
}
- }
- if (ps.name != null) {
- await this.pagesRepository.findBy({
- id: Not(ps.pageId),
- userId: me.id,
- name: ps.name,
- }).then(result => {
- if (result.length > 0) {
- throw new ApiError(meta.errors.nameAlreadyExists);
- }
+ await this.pageService.update(me, ps.pageId, {
+ ...ps,
+ eyeCatchingImage,
});
+ } catch (err) {
+ if (err instanceof IdentifiableError) {
+ if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage);
+ if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied);
+ if (err.id === 'd05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4') throw new ApiError(meta.errors.nameAlreadyExists);
+ }
+ throw err;
}
-
- await this.pagesRepository.update(page.id, {
- updatedAt: new Date(),
- title: ps.title,
- name: ps.name,
- summary: ps.summary === undefined ? page.summary : ps.summary,
- content: ps.content,
- variables: ps.variables,
- script: ps.script,
- alignCenter: ps.alignCenter,
- hideTitleWhenPinned: ps.hideTitleWhenPinned,
- font: ps.font,
- eyeCatchingImageId: ps.eyeCatchingImageId,
- });
});
}
}