From 4b1c931503116ad8bb3af5a4da74906b6f70ab76 Mon Sep 17 00:00:00 2001 From: forehalo Date: Fri, 7 Feb 2025 07:31:56 +0000 Subject: [PATCH] fix(server): default page owner (#10015) --- .../server/src/base/graphql/pagination.ts | 17 +++++---- .../server/src/base/graphql/register.ts | 14 +++++-- .../server/src/core/doc/adapters/workspace.ts | 38 ++++++++++++++++--- .../server/src/core/permission/service.ts | 23 +++++++---- .../src/core/workspaces/resolvers/page.ts | 26 +++++++++++-- .../core/workspaces/resolvers/workspace.ts | 10 ++++- packages/backend/server/src/schema.gql | 5 --- 7 files changed, 97 insertions(+), 36 deletions(-) diff --git a/packages/backend/server/src/base/graphql/pagination.ts b/packages/backend/server/src/base/graphql/pagination.ts index de28cc3625..b8cfe9bef5 100644 --- a/packages/backend/server/src/base/graphql/pagination.ts +++ b/packages/backend/server/src/base/graphql/pagination.ts @@ -39,15 +39,16 @@ export class PaginationInput { 'returns the elements in the list that come after the specified cursor.', middleware: [parseCursorMiddleware], }) - after!: string | null; + after?: string | null; - @Field(() => String, { - nullable: true, - description: - 'returns the elements in the list that come before the specified cursor.', - middleware: [parseCursorMiddleware], - }) - before!: string | null; + // NOT IMPLEMENTED YET + // @Field(() => String, { + // nullable: true, + // description: + // 'returns the elements in the list that come before the specified cursor.', + // middleware: [parseCursorMiddleware], + // }) + // before?: string | null; } const encode = (input: string) => Buffer.from(input).toString('base64'); diff --git a/packages/backend/server/src/base/graphql/register.ts b/packages/backend/server/src/base/graphql/register.ts index 1155c8dda5..0e5e283960 100644 --- a/packages/backend/server/src/base/graphql/register.ts +++ b/packages/backend/server/src/base/graphql/register.ts @@ -1,17 +1,23 @@ import { Type } from '@nestjs/common'; -import { Field, ObjectType } from '@nestjs/graphql'; +import { Field, FieldOptions, ObjectType } from '@nestjs/graphql'; import { ApplyType } from '../utils/types'; export function registerObjectType( - fields: Record>, + fields: Record< + string, + { + type: () => Type; + options?: FieldOptions; + } + >, options: { name: string; } ) { const Inner = ApplyType(); - for (const [key, value] of Object.entries(fields)) { - Field(() => value)(Inner.prototype, key); + for (const [key, { type, options }] of Object.entries(fields)) { + Field(type, options)(Inner.prototype, key); } ObjectType(options.name)(Inner); diff --git a/packages/backend/server/src/core/doc/adapters/workspace.ts b/packages/backend/server/src/core/doc/adapters/workspace.ts index d571ff1417..76486099d4 100644 --- a/packages/backend/server/src/core/doc/adapters/workspace.ts +++ b/packages/backend/server/src/core/doc/adapters/workspace.ts @@ -32,7 +32,7 @@ declare global { workspaceId: string; docId: string; }; - 'doc.update.pushed': { + 'doc.created': { workspaceId: string; docId: string; editor?: string; @@ -63,6 +63,8 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { return 0; } + const isNewDoc = !(await this.docExisted(workspaceId, docId)); + let pendings = updates; let done = 0; let timestamp = Date.now(); @@ -110,11 +112,14 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { await this.updateCachedUpdatesCount(workspaceId, docId, batch.length); } }); - this.event.emit('doc.update.pushed', { - workspaceId, - docId, - editor: editorId, - }); + + if (isNewDoc) { + this.event.emit('doc.created', { + workspaceId, + docId, + editor: editorId, + }); + } } catch (e) { this.logger.error('Failed to insert doc updates', e); metrics.doc.counter('doc_update_insert_failed').add(1); @@ -589,6 +594,27 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { } } + private async docExisted(workspaceId: string, docId: string) { + const snapshot = await this.db.snapshot.count({ + where: { + workspaceId, + id: docId, + }, + }); + + if (snapshot > 0) { + return true; + } + + const updates = await this.db.update.count({ + where: { + workspaceId, + id: docId, + }, + }); + + return updates > 0; + } /** * @deprecated */ diff --git a/packages/backend/server/src/core/permission/service.ts b/packages/backend/server/src/core/permission/service.ts index 671da4363c..bcaf45b912 100644 --- a/packages/backend/server/src/core/permission/service.ts +++ b/packages/backend/server/src/core/permission/service.ts @@ -30,16 +30,23 @@ export class PermissionService { private readonly event: EventBus ) {} - @OnEvent('doc.update.pushed') - async onDocUpdatePushed(payload: Events['doc.update.pushed']) { + @OnEvent('doc.created') + async setDefaultPageOwner(payload: Events['doc.created']) { const { workspaceId, docId, editor } = payload; - await this.prisma.$queryRaw` - INSERT INTO "workspace_page_user_permissions" ("workspace_id", "page_id", "user_id", "type", "created_at") - VALUES (${workspaceId}, ${docId}, ${editor}, ${DocRole.Owner}, now()) - ON CONFLICT ("workspace_id", "page_id", "user_id") - DO NOTHING - `; + if (!editor) { + return; + } + + await this.prisma.workspacePageUserPermission.createMany({ + data: { + workspaceId, + pageId: docId, + userId: editor, + type: DocRole.Owner, + createdAt: new Date(), + }, + }); } private get acceptedCondition() { diff --git a/packages/backend/server/src/core/workspaces/resolvers/page.ts b/packages/backend/server/src/core/workspaces/resolvers/page.ts index d095c5f16a..a188f35829 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/page.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/page.ts @@ -130,7 +130,15 @@ class PaginatedGrantedDocUserType extends Paginated(GrantedDocUserType) {} const DocPermissions = registerObjectType( Object.fromEntries( - DOC_ACTIONS.map(action => [action.replaceAll('.', '_'), Boolean]) + DOC_ACTIONS.map(action => [ + action, + { + type: () => Boolean, + options: { + name: action.replaceAll('.', '_'), + }, + }, + ]) ), { name: 'DocPermissions' } ); @@ -281,10 +289,20 @@ export class PagePermissionResolver { where: { workspaceId: workspace.id, pageId: docId.guid, + createdAt: pagination.after + ? { + gt: pagination.after, + } + : undefined, }, - orderBy: { - createdAt: 'desc', - }, + orderBy: [ + { + type: 'desc', + }, + { + createdAt: 'desc', + }, + ], take: pagination.first, skip: pagination.offset, }), diff --git a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts index 1986753555..7a4267a103 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts @@ -77,7 +77,15 @@ class WorkspacePageMeta { const WorkspacePermissions = registerObjectType( Object.fromEntries( - WORKSPACE_ACTIONS.map(action => [action.replaceAll('.', '_'), Boolean]) + WORKSPACE_ACTIONS.map(action => [ + action, + { + type: () => Boolean, + options: { + name: action.replaceAll('.', '_'), + }, + }, + ]) ), { name: 'WorkspacePermissions' } ); diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index 436a6edc35..4d3e7f5b5f 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -699,11 +699,6 @@ input PaginationInput { """returns the elements in the list that come after the specified cursor.""" after: String - """ - returns the elements in the list that come before the specified cursor. - """ - before: String - """returns the first n elements from the list.""" first: Int = 10