refactor: rename all page query to doc (#10019)

This commit is contained in:
forehalo
2025-02-07 11:40:59 +00:00
parent 4e00ddd5f1
commit 0b9d30b55a
34 changed files with 658 additions and 643 deletions

View File

@@ -25,16 +25,16 @@ model User {
features UserFeature[]
userStripeCustomer UserStripeCustomer?
workspacePermissions WorkspaceUserPermission[]
pagePermissions WorkspacePageUserPermission[]
docPermissions WorkspaceDocUserPermission[]
connectedAccounts ConnectedAccount[]
sessions UserSession[]
aiSessions AiSession[]
updatedRuntimeConfigs RuntimeConfig[]
userSnapshots UserSnapshot[]
createdSnapshot Snapshot[] @relation("createdSnapshot")
updatedSnapshot Snapshot[] @relation("updatedSnapshot")
createdUpdate Update[] @relation("createdUpdate")
createdHistory SnapshotHistory[] @relation("createdHistory")
createdSnapshot Snapshot[] @relation("createdSnapshot")
updatedSnapshot Snapshot[] @relation("updatedSnapshot")
createdUpdate Update[] @relation("createdUpdate")
createdHistory SnapshotHistory[] @relation("createdHistory")
@@index([email])
@@map("users")
@@ -102,11 +102,11 @@ model Workspace {
enableAi Boolean @default(true) @map("enable_ai")
enableUrlPreview Boolean @default(false) @map("enable_url_preview")
features WorkspaceFeature[]
pages WorkspacePage[]
permissions WorkspaceUserPermission[]
pagePermissions WorkspacePageUserPermission[]
blobs Blob[]
features WorkspaceFeature[]
docs WorkspaceDoc[]
permissions WorkspaceUserPermission[]
docPermissions WorkspaceDocUserPermission[]
blobs Blob[]
@@map("workspaces")
}
@@ -116,18 +116,18 @@ model Workspace {
// We won't make sure every page has a corresponding record in this table.
// Only the ones that have ever changed will have records here,
// and for others we will make sure it's has a default value return in our business logic.
model WorkspacePage {
workspaceId String @map("workspace_id") @db.VarChar
pageId String @map("page_id") @db.VarChar
public Boolean @default(false)
model WorkspaceDoc {
workspaceId String @map("workspace_id") @db.VarChar
docId String @map("page_id") @db.VarChar
public Boolean @default(false)
// Workspace user's default role in this page, default is `Manager`
defaultRole Int @default(30) @db.SmallInt
defaultRole Int @default(30) @db.SmallInt
// Page/Edgeless
mode Int @default(0) @db.SmallInt
mode Int @default(0) @db.SmallInt
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@id([workspaceId, pageId])
@@id([workspaceId, docId])
@@map("workspace_pages")
}
@@ -162,9 +162,9 @@ model WorkspaceUserPermission {
@@map("workspace_user_permissions")
}
model WorkspacePageUserPermission {
model WorkspaceDocUserPermission {
workspaceId String @map("workspace_id") @db.VarChar
pageId String @map("page_id") @db.VarChar
docId String @map("page_id") @db.VarChar
userId String @map("user_id") @db.VarChar
// External/Reader/Editor/Manager/Owner
type Int @db.SmallInt
@@ -173,7 +173,7 @@ model WorkspacePageUserPermission {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@id([workspaceId, pageId, userId])
@@id([workspaceId, docId, userId])
@@map("workspace_page_user_permissions")
}

View File

@@ -49,7 +49,7 @@ test.after(async t => {
test('should create page with default mode and public false', async t => {
const page = await t.context.page.upsert(workspace.id, 'page1');
t.is(page.workspaceId, workspace.id);
t.is(page.pageId, 'page1');
t.is(page.docId, 'page1');
t.is(page.mode, PublicPageMode.Page);
t.is(page.public, false);
});
@@ -110,7 +110,7 @@ test('should get public pages of a workspace', async t => {
await t.context.page.upsert(workspace.id, 'page3');
const pages = await t.context.page.findPublics(workspace.id);
t.is(pages.length, 2);
t.deepEqual(pages.map(p => p.pageId).sort(), ['page1', 'page2']);
t.deepEqual(pages.map(p => p.docId).sort(), ['page1', 'page2']);
});
test('should grant a member to access a page', async t => {

View File

@@ -23,6 +23,7 @@ import {
createInviteLink,
createTestingApp,
createWorkspace,
docGrantedUsersList,
getInviteInfo,
getInviteLink,
getWorkspace,
@@ -31,7 +32,6 @@ import {
inviteUser,
inviteUsers,
leaveWorkspace,
pageGrantedUsersList,
revokeDocUserRoles,
revokeInviteLink,
revokeMember,
@@ -39,7 +39,7 @@ import {
signUp,
sleep,
TestingApp,
updatePageDefaultRole,
updateDocDefaultRole,
UserAuthedType,
} from './utils';
@@ -817,13 +817,13 @@ test('should be able to grant and revoke users role in page', async t => {
read,
external,
} = await init(app, 5);
const pageId = nanoid();
const docId = nanoid();
const res = await grantDocUserRoles(
app,
admin.token.token,
ws.id,
pageId,
docId,
[read.id, write.id],
DocRole.Manager
);
@@ -840,7 +840,7 @@ test('should be able to grant and revoke users role in page', async t => {
app,
admin.token.token,
ws.id,
pageId,
docId,
[read.id],
DocRole.Reader
);
@@ -849,7 +849,7 @@ test('should be able to grant and revoke users role in page', async t => {
app,
read.token.token,
ws.id,
pageId,
docId,
[external.id],
DocRole.Editor
);
@@ -859,15 +859,15 @@ test('should be able to grant and revoke users role in page', async t => {
},
});
const pageUsersList = await pageGrantedUsersList(
const docUsersList = await docGrantedUsersList(
app,
admin.token.token,
ws.id,
pageId
docId
);
t.is(pageUsersList.data.workspace.pageGrantedUsersList.totalCount, 3);
t.is(docUsersList.data.workspace.doc.grantedUsersList.totalCount, 3);
const externalRole =
pageUsersList.data.workspace.pageGrantedUsersList.edges.find(
docUsersList.data.workspace.doc.grantedUsersList.edges.find(
(edge: any) => edge.node.user.id === external.id
)?.node.role;
t.is(externalRole, DocRole[DocRole.Editor]);
@@ -877,18 +877,18 @@ test('should be able to grant and revoke users role in page', async t => {
test('should be able to change the default role in page', async t => {
const { app } = t.context;
const { teamWorkspace: ws, admin } = await init(app, 5);
const pageId = nanoid();
const res = await updatePageDefaultRole(
const docId = nanoid();
const res = await updateDocDefaultRole(
app,
admin.token.token,
ws.id,
pageId,
docId,
DocRole.Reader
);
t.deepEqual(res.body, {
data: {
updatePageDefaultRole: true,
updateDocDefaultRole: true,
},
});
});
@@ -902,53 +902,53 @@ test('default page role should be able to override the workspace role', async t
external,
} = await init(app, 5);
const pageId = nanoid();
const docId = nanoid();
const res = await updatePageDefaultRole(
const res = await updateDocDefaultRole(
app,
admin.token.token,
workspace.id,
pageId,
docId,
DocRole.Manager
);
t.deepEqual(res.body, {
data: {
updatePageDefaultRole: true,
updateDocDefaultRole: true,
},
});
// reader can manage the page if the page default role is Manager
{
const readerRes = await updatePageDefaultRole(
const readerRes = await updateDocDefaultRole(
app,
read.token.token,
workspace.id,
pageId,
docId,
DocRole.Manager
);
t.deepEqual(readerRes.body, {
data: {
updatePageDefaultRole: true,
updateDocDefaultRole: true,
},
});
}
// external can't manage the page even if the page default role is Manager
{
const externalRes = await updatePageDefaultRole(
const externalRes = await updateDocDefaultRole(
app,
external.token.token,
workspace.id,
pageId,
docId,
DocRole.Manager
);
t.like(externalRes.body, {
errors: [
{
message: `You do not have permission to access doc ${pageId} under Space ${workspace.id}.`,
message: `You do not have permission to access doc ${docId} under Space ${workspace.id}.`,
},
],
});
@@ -958,13 +958,13 @@ test('default page role should be able to override the workspace role', async t
test('should be able to grant and revoke doc user role', async t => {
const { app } = t.context;
const { teamWorkspace: ws, admin, read, external } = await init(app, 5);
const pageId = nanoid();
const docId = nanoid();
const res = await grantDocUserRoles(
app,
admin.token.token,
ws.id,
pageId,
docId,
[external.id],
DocRole.Manager
);
@@ -981,7 +981,7 @@ test('should be able to grant and revoke doc user role', async t => {
app,
external.token.token,
ws.id,
pageId,
docId,
[read.id],
DocRole.Manager
);
@@ -999,7 +999,7 @@ test('should be able to grant and revoke doc user role', async t => {
app,
admin.token.token,
ws.id,
pageId,
docId,
external.id
);
@@ -1014,14 +1014,14 @@ test('should be able to grant and revoke doc user role', async t => {
app,
external.token.token,
ws.id,
pageId,
docId,
read.id
);
t.like(externalRes.body, {
errors: [
{
message: `You do not have permission to access doc ${pageId} under Space ${ws.id}.`,
message: `You do not have permission to access doc ${docId} under Space ${ws.id}.`,
},
],
});
@@ -1031,7 +1031,7 @@ test('should be able to grant and revoke doc user role', async t => {
test('update page default role should throw error if the space does not exist', async t => {
const { app } = t.context;
const { admin } = await init(app, 5);
const pageId = nanoid();
const docId = nanoid();
const nonExistWorkspaceId = 'non-exist-workspace';
const res = await request(app.getHttpServer())
.post('/graphql')
@@ -1040,9 +1040,9 @@ test('update page default role should throw error if the space does not exist',
.send({
query: `
mutation {
updatePageDefaultRole(input: {
updateDocDefaultRole(input: {
workspaceId: "${nonExistWorkspaceId}",
docId: "${pageId}",
docId: "${docId}",
role: Manager,
})
}
@@ -1052,7 +1052,7 @@ test('update page default role should throw error if the space does not exist',
t.like(res.body, {
errors: [
{
message: `You do not have permission to access doc ${pageId} under Space ${nonExistWorkspaceId}.`,
message: `You do not have permission to access doc ${docId} under Space ${nonExistWorkspaceId}.`,
},
],
});

View File

@@ -54,7 +54,7 @@ export function revokeDocUserRoles(
});
}
export function updatePageDefaultRole(
export function updateDocDefaultRole(
app: INestApplication,
token: string,
workspaceId: string,
@@ -68,7 +68,7 @@ export function updatePageDefaultRole(
.send({
query: `
mutation {
updatePageDefaultRole(input: {
updateDocDefaultRole(input: {
workspaceId: "${workspaceId}",
docId: "${docId}",
role: ${DocRole[role]}
@@ -78,7 +78,7 @@ export function updatePageDefaultRole(
});
}
export async function pageGrantedUsersList(
export async function docGrantedUsersList(
app: INestApplication,
token: string,
workspaceId: string,
@@ -94,14 +94,16 @@ export async function pageGrantedUsersList(
query: `
query {
workspace(id: "${workspaceId}") {
pageGrantedUsersList(pageId: "${docId}", pagination: { first: ${first}, offset: ${offset} }) {
totalCount
edges {
doc(docId: "${docId}") {
grantedUsersList(pagination: { first: ${first}, offset: ${offset} }) {
totalCount
edges {
cursor
node {
role
user {
id
node {
role
user {
id
}
}
}
}

View File

@@ -103,11 +103,11 @@ export async function updateWorkspace(
return res.body.data.updateWorkspace.public;
}
export async function publishPage(
export async function publishDoc(
app: INestApplication,
token: string,
workspaceId: string,
pageId: string
docId: string
) {
const res = await request(app.getHttpServer())
.post(gql)
@@ -116,7 +116,7 @@ export async function publishPage(
.send({
query: `
mutation {
publishPage(workspaceId: "${workspaceId}", pageId: "${pageId}") {
publishDoc(workspaceId: "${workspaceId}", docId: "${docId}") {
id
mode
}
@@ -124,14 +124,14 @@ export async function publishPage(
`,
})
.expect(200);
return res.body.errors?.[0]?.message || res.body.data?.publishPage;
return res.body.errors?.[0]?.message || res.body.data?.publishDoc;
}
export async function revokePublicPage(
export async function revokePublicDoc(
app: INestApplication,
token: string,
workspaceId: string,
pageId: string
docId: string
) {
const res = await request(app.getHttpServer())
.post(gql)
@@ -140,7 +140,7 @@ export async function revokePublicPage(
.send({
query: `
mutation {
revokePublicPage(workspaceId: "${workspaceId}", pageId: "${pageId}") {
revokePublicDoc(workspaceId: "${workspaceId}", docId: "${docId}") {
id
mode
public
@@ -149,7 +149,7 @@ export async function revokePublicPage(
`,
})
.expect(200);
return res.body.errors?.[0]?.message || res.body.data?.revokePublicPage;
return res.body.errors?.[0]?.message || res.body.data?.revokePublicDoc;
}
export async function grantMember(

View File

@@ -10,8 +10,8 @@ import {
createWorkspace,
getWorkspacePublicPages,
inviteUser,
publishPage,
revokePublicPage,
publishDoc,
revokePublicDoc,
signUp,
TestingApp,
updateWorkspace,
@@ -83,8 +83,8 @@ test('should share a page', async t => {
const workspace = await createWorkspace(app, u1.token.token);
const share = await publishPage(app, u1.token.token, workspace.id, 'page1');
t.is(share.id, 'page1', 'failed to share page');
const share = await publishDoc(app, u1.token.token, workspace.id, 'doc1');
t.is(share.id, 'doc1', 'failed to share doc');
const pages = await getWorkspacePublicPages(
app,
u1.token.token,
@@ -93,8 +93,8 @@ test('should share a page', async t => {
t.is(pages.length, 1, 'failed to get shared pages');
t.deepEqual(
pages[0],
{ id: 'page1', mode: 'Page' },
'failed to get shared page: page1'
{ id: 'doc1', mode: 'Page' },
'failed to get shared doc: doc1'
);
const resp1 = await request(app.getHttpServer())
@@ -107,53 +107,48 @@ test('should share a page', async t => {
t.is(resp2.statusCode, 200, 'failed to get root doc with public pages');
const resp3 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/page1`)
.get(`/api/workspaces/${workspace.id}/docs/doc1`)
.auth(u1.token.token, { type: 'bearer' });
// 404 because we don't put the page doc to server
t.is(resp3.statusCode, 404, 'failed to get shared doc with u1 token');
const resp4 = await request(app.getHttpServer()).get(
`/api/workspaces/${workspace.id}/docs/page1`
`/api/workspaces/${workspace.id}/docs/doc1`
);
// 404 because we don't put the page doc to server
t.is(resp4.statusCode, 404, 'should not get shared doc without token');
const msg1 = await publishPage(app, u2.token.token, 'not_exists_ws', 'page2');
const msg1 = await publishDoc(app, u2.token.token, 'not_exists_ws', 'doc2');
t.is(
msg1,
'You do not have permission to access doc page2 under Space not_exists_ws.',
'unauthorized user can share page'
'You do not have permission to access doc doc2 under Space not_exists_ws.',
'unauthorized user can share doc'
);
const msg2 = await revokePublicPage(
const msg2 = await revokePublicDoc(
app,
u2.token.token,
'not_exists_ws',
'page2'
'doc2'
);
t.is(
msg2,
'You do not have permission to access doc page2 under Space not_exists_ws.',
'unauthorized user can share page'
'You do not have permission to access doc doc2 under Space not_exists_ws.',
'unauthorized user can share doc'
);
const revoke = await revokePublicPage(
const revoke = await revokePublicDoc(
app,
u1.token.token,
workspace.id,
'page1'
'doc1'
);
t.false(revoke.public, 'failed to revoke page');
t.false(revoke.public, 'failed to revoke doc');
const pages2 = await getWorkspacePublicPages(
app,
u1.token.token,
workspace.id
);
t.is(pages2.length, 0, 'failed to get shared pages');
const msg4 = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page3'
);
t.is(msg4, 'Page is not public');
const msg4 = await revokePublicDoc(app, u1.token.token, workspace.id, 'doc3');
t.is(msg4, 'Doc is not public');
const pages3 = await getWorkspacePublicPages(
app,

View File

@@ -41,7 +41,7 @@ test.before(async t => {
t.context.storage = app.get(WorkspaceBlobStorage);
t.context.workspace = app.get(PgWorkspaceDocStorageAdapter);
await db.workspacePage.create({
await db.workspaceDoc.create({
data: {
workspace: {
create: {
@@ -49,12 +49,12 @@ test.before(async t => {
public: true,
},
},
pageId: 'private',
docId: 'private',
public: false,
},
});
await db.workspacePage.create({
await db.workspaceDoc.create({
data: {
workspace: {
create: {
@@ -62,12 +62,12 @@ test.before(async t => {
public: false,
},
},
pageId: 'public',
docId: 'public',
public: true,
},
});
await db.workspacePage.create({
await db.workspaceDoc.create({
data: {
workspace: {
create: {
@@ -75,7 +75,7 @@ test.before(async t => {
public: false,
},
},
pageId: 'private',
docId: 'private',
public: false,
},
});
@@ -261,8 +261,8 @@ test('should be able to change page publish mode', async t => {
t.is(res.status, HttpStatus.OK);
t.is(res.get('publish-mode'), 'page');
await db.workspacePage.update({
where: { workspaceId_pageId: { workspaceId: 'private', pageId: 'public' } },
await db.workspaceDoc.update({
where: { workspaceId_docId: { workspaceId: 'private', docId: 'public' } },
data: { mode: 1 },
});

View File

@@ -428,13 +428,13 @@ export const USER_FRIENDLY_ERRORS = {
message: ({ spaceId, blobId }) =>
`Blob ${blobId} not found in Space ${spaceId}.`,
},
expect_to_publish_page: {
expect_to_publish_doc: {
type: 'invalid_input',
message: 'Expected to publish a page, not a Space.',
message: 'Expected to publish a doc, not a Space.',
},
expect_to_revoke_public_page: {
expect_to_revoke_public_doc: {
type: 'invalid_input',
message: 'Expected to revoke a public page, not a Space.',
message: 'Expected to revoke a public doc, not a Space.',
},
expect_to_grant_doc_user_roles: {
type: 'invalid_input',
@@ -454,9 +454,9 @@ export const USER_FRIENDLY_ERRORS = {
message: ({ spaceId, docId }) =>
`Expect update roles on doc ${docId} under Space ${spaceId}, not a Space.`,
},
page_is_not_public: {
doc_is_not_public: {
type: 'bad_request',
message: 'Page is not public.',
message: 'Doc is not public.',
},
failed_to_save_updates: {
type: 'internal_server_error',
@@ -470,13 +470,13 @@ export const USER_FRIENDLY_ERRORS = {
type: 'action_forbidden',
message: 'A Team workspace is required to perform this action.',
},
page_default_role_can_not_be_owner: {
doc_default_role_can_not_be_owner: {
type: 'invalid_input',
message: 'Page default role can not be owner.',
message: 'Doc default role can not be owner.',
},
can_not_batch_grant_page_owner_permissions: {
can_not_batch_grant_doc_owner_permissions: {
type: 'invalid_input',
message: 'Can not batch grant page owner permissions.',
message: 'Can not batch grant doc owner permissions.',
},
// Subscription Errors

View File

@@ -337,15 +337,15 @@ export class BlobNotFound extends UserFriendlyError {
}
}
export class ExpectToPublishPage extends UserFriendlyError {
export class ExpectToPublishDoc extends UserFriendlyError {
constructor(message?: string) {
super('invalid_input', 'expect_to_publish_page', message);
super('invalid_input', 'expect_to_publish_doc', message);
}
}
export class ExpectToRevokePublicPage extends UserFriendlyError {
export class ExpectToRevokePublicDoc extends UserFriendlyError {
constructor(message?: string) {
super('invalid_input', 'expect_to_revoke_public_page', message);
super('invalid_input', 'expect_to_revoke_public_doc', message);
}
}
@ObjectType()
@@ -382,9 +382,9 @@ export class ExpectToUpdateDocUserRole extends UserFriendlyError {
}
}
export class PageIsNotPublic extends UserFriendlyError {
export class DocIsNotPublic extends UserFriendlyError {
constructor(message?: string) {
super('bad_request', 'page_is_not_public', message);
super('bad_request', 'doc_is_not_public', message);
}
}
@@ -406,15 +406,15 @@ export class ActionForbiddenOnNonTeamWorkspace extends UserFriendlyError {
}
}
export class PageDefaultRoleCanNotBeOwner extends UserFriendlyError {
export class DocDefaultRoleCanNotBeOwner extends UserFriendlyError {
constructor(message?: string) {
super('invalid_input', 'page_default_role_can_not_be_owner', message);
super('invalid_input', 'doc_default_role_can_not_be_owner', message);
}
}
export class CanNotBatchGrantPageOwnerPermissions extends UserFriendlyError {
export class CanNotBatchGrantDocOwnerPermissions extends UserFriendlyError {
constructor(message?: string) {
super('invalid_input', 'can_not_batch_grant_page_owner_permissions', message);
super('invalid_input', 'can_not_batch_grant_doc_owner_permissions', message);
}
}
@ObjectType()
@@ -764,17 +764,17 @@ export enum ErrorNames {
INVALID_HISTORY_TIMESTAMP,
DOC_HISTORY_NOT_FOUND,
BLOB_NOT_FOUND,
EXPECT_TO_PUBLISH_PAGE,
EXPECT_TO_REVOKE_PUBLIC_PAGE,
EXPECT_TO_PUBLISH_DOC,
EXPECT_TO_REVOKE_PUBLIC_DOC,
EXPECT_TO_GRANT_DOC_USER_ROLES,
EXPECT_TO_REVOKE_DOC_USER_ROLES,
EXPECT_TO_UPDATE_DOC_USER_ROLE,
PAGE_IS_NOT_PUBLIC,
DOC_IS_NOT_PUBLIC,
FAILED_TO_SAVE_UPDATES,
FAILED_TO_UPSERT_SNAPSHOT,
ACTION_FORBIDDEN_ON_NON_TEAM_WORKSPACE,
PAGE_DEFAULT_ROLE_CAN_NOT_BE_OWNER,
CAN_NOT_BATCH_GRANT_PAGE_OWNER_PERMISSIONS,
DOC_DEFAULT_ROLE_CAN_NOT_BE_OWNER,
CAN_NOT_BATCH_GRANT_DOC_OWNER_PERMISSIONS,
UNSUPPORTED_SUBSCRIPTION_PLAN,
FAILED_TO_CHECKOUT,
INVALID_CHECKOUT_PARAMETERS,

View File

@@ -16,7 +16,7 @@ export {
fixupDocRole,
mapDocRoleToPermissions,
mapWorkspaceRoleToPermissions,
PublicPageMode,
PublicDocMode,
WORKSPACE_ACTIONS,
type WorkspaceActionPermissions,
WorkspaceRole,

View File

@@ -1,10 +1,10 @@
import { Injectable, Logger } from '@nestjs/common';
import type { Prisma, WorkspacePageUserPermission } from '@prisma/client';
import type { Prisma, WorkspaceDocUserPermission } from '@prisma/client';
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
import { groupBy } from 'lodash-es';
import {
CanNotBatchGrantPageOwnerPermissions,
CanNotBatchGrantDocOwnerPermissions,
DocAccessDenied,
EventBus,
OnEvent,
@@ -17,7 +17,7 @@ import {
docActionRequiredRole,
docActionRequiredWorkspaceRole,
DocRole,
PublicPageMode,
PublicDocMode,
WorkspaceRole,
} from './types';
@@ -38,10 +38,10 @@ export class PermissionService {
return;
}
await this.prisma.workspacePageUserPermission.createMany({
await this.prisma.workspaceDocUserPermission.createMany({
data: {
workspaceId,
pageId: docId,
docId,
userId: editor,
type: DocRole.Owner,
createdAt: new Date(),
@@ -172,7 +172,7 @@ export class PermissionService {
// if workspace is public or have any public page, then allow to access
const [isPublicWorkspace, publicPages] = await Promise.all([
this.tryCheckWorkspace(ws, user, WorkspaceRole.Collaborator),
this.prisma.workspacePage.count({
this.prisma.workspaceDoc.count({
where: {
workspaceId: ws,
public: true,
@@ -555,17 +555,17 @@ export class PermissionService {
async tryCheckPage(
ws: string,
page: string,
doc: string,
action: DocAction,
user?: string
) {
const role = docActionRequiredRole(action);
// check whether page is public
if (action === 'Doc.Read') {
const count = await this.prisma.workspacePage.count({
const count = await this.prisma.workspaceDoc.count({
where: {
workspaceId: ws,
pageId: page,
docId: doc,
public: true,
},
});
@@ -579,20 +579,20 @@ export class PermissionService {
if (user) {
const [roleEntity, pageEntity, workspaceRoleEntity] = await Promise.all([
this.prisma.workspacePageUserPermission.findFirst({
this.prisma.workspaceDocUserPermission.findFirst({
where: {
workspaceId: ws,
pageId: page,
docId: doc,
userId: user,
},
select: {
type: true,
},
}),
this.prisma.workspacePage.findFirst({
this.prisma.workspaceDoc.findFirst({
where: {
workspaceId: ws,
pageId: page,
docId: doc,
},
select: {
defaultRole: true,
@@ -629,7 +629,7 @@ export class PermissionService {
}
this.logger.log("User's role is lower than required", {
workspaceId: ws,
docId: page,
docId: doc,
userId: user,
workspaceRole: workspaceRoleEntity
? WorkspaceRole[workspaceRoleEntity.type]
@@ -651,24 +651,24 @@ export class PermissionService {
);
}
async isPublicPage(ws: string, page: string) {
return this.prisma.workspacePage
async isPublicPage(ws: string, doc: string) {
return this.prisma.workspaceDoc
.count({
where: {
workspaceId: ws,
pageId: page,
docId: doc,
public: true,
},
})
.then(count => count > 0);
}
async publishPage(ws: string, page: string, mode = PublicPageMode.Page) {
return this.prisma.workspacePage.upsert({
async publishPage(ws: string, doc: string, mode = PublicDocMode.Page) {
return this.prisma.workspaceDoc.upsert({
where: {
workspaceId_pageId: {
workspaceId_docId: {
workspaceId: ws,
pageId: page,
docId: doc,
},
},
update: {
@@ -677,19 +677,19 @@ export class PermissionService {
},
create: {
workspaceId: ws,
pageId: page,
docId: doc,
mode,
public: true,
},
});
}
async revokePublicPage(ws: string, page: string) {
return this.prisma.workspacePage.upsert({
async revokePublicPage(ws: string, doc: string) {
return this.prisma.workspaceDoc.upsert({
where: {
workspaceId_pageId: {
workspaceId_docId: {
workspaceId: ws,
pageId: page,
docId: doc,
},
},
update: {
@@ -697,20 +697,20 @@ export class PermissionService {
},
create: {
workspaceId: ws,
pageId: page,
docId: doc,
public: false,
},
});
}
async grantPage(ws: string, page: string, user: string, permission: DocRole) {
async grantPage(ws: string, doc: string, user: string, permission: DocRole) {
const [p] = await this.prisma.$transaction(
[
this.prisma.workspacePageUserPermission.upsert({
this.prisma.workspaceDocUserPermission.upsert({
where: {
workspaceId_pageId_userId: {
workspaceId_docId_userId: {
workspaceId: ws,
pageId: page,
docId: doc,
userId: user,
},
},
@@ -719,7 +719,7 @@ export class PermissionService {
},
create: {
workspaceId: ws,
pageId: page,
docId: doc,
userId: user,
type: permission,
},
@@ -727,10 +727,10 @@ export class PermissionService {
// If the new permission is owner, we need to revoke old owner
permission === DocRole.Owner
? this.prisma.workspacePageUserPermission.updateMany({
? this.prisma.workspaceDocUserPermission.updateMany({
where: {
workspaceId: ws,
pageId: page,
docId: doc,
type: DocRole.Owner,
userId: {
not: user,
@@ -744,14 +744,14 @@ export class PermissionService {
].filter(Boolean) as Prisma.PrismaPromise<any>[]
);
return p as WorkspacePageUserPermission;
return p as WorkspaceDocUserPermission;
}
async revokePage(ws: string, page: string, user: string) {
const result = await this.prisma.workspacePageUserPermission.deleteMany({
async revokePage(ws: string, doc: string, user: string) {
const result = await this.prisma.workspaceDocUserPermission.deleteMany({
where: {
workspaceId: ws,
pageId: page,
docId: doc,
userId: user,
type: {
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading
@@ -765,7 +765,7 @@ export class PermissionService {
async batchGrantPage(
workspaceId: string,
pageId: string,
docId: string,
userIds: string[],
role: DocRole
) {
@@ -774,14 +774,14 @@ export class PermissionService {
}
if (role === DocRole.Owner) {
throw new CanNotBatchGrantPageOwnerPermissions();
throw new CanNotBatchGrantDocOwnerPermissions();
}
const result = await this.prisma.workspacePageUserPermission.createMany({
const result = await this.prisma.workspaceDocUserPermission.createMany({
skipDuplicates: true,
data: userIds.map(id => ({
workspaceId,
pageId,
docId,
userId: id,
type: role,
})),

View File

@@ -1,6 +1,6 @@
import { LeafPaths, LeafVisitor } from '../../base';
export enum PublicPageMode {
export enum PublicDocMode {
Page,
Edgeless,
}

View File

@@ -13,7 +13,7 @@ import {
} from '../../base';
import { CurrentUser, Public } from '../auth';
import { PgWorkspaceDocStorageAdapter } from '../doc';
import { PermissionService, PublicPageMode } from '../permission';
import { PermissionService, PublicDocMode } from '../permission';
import { WorkspaceBlobStorage } from '../storage';
import { DocID } from '../utils/doc';
@@ -109,16 +109,16 @@ export class WorkspacesController {
if (!docId.isWorkspace) {
// fetch the publish page mode for publish page
const publishPage = await this.prisma.workspacePage.findUnique({
const publishPage = await this.prisma.workspaceDoc.findUnique({
where: {
workspaceId_pageId: {
workspaceId_docId: {
workspaceId: docId.workspace,
pageId: docId.guid,
docId: docId.guid,
},
},
});
const publishPageMode =
publishPage?.mode === PublicPageMode.Edgeless ? 'edgeless' : 'page';
publishPage?.mode === PublicDocMode.Edgeless ? 'edgeless' : 'page';
res.setHeader('publish-mode', publishPageMode);
}

View File

@@ -10,9 +10,10 @@ import { UserModule } from '../user';
import { WorkspacesController } from './controller';
import {
DocHistoryResolver,
PagePermissionResolver,
DocResolver,
TeamWorkspaceResolver,
WorkspaceBlobResolver,
WorkspaceDocResolver,
WorkspaceResolver,
WorkspaceService,
} from './resolvers';
@@ -31,7 +32,8 @@ import {
providers: [
WorkspaceResolver,
TeamWorkspaceResolver,
PagePermissionResolver,
WorkspaceDocResolver,
DocResolver,
DocHistoryResolver,
WorkspaceBlobResolver,
WorkspaceService,

View File

@@ -10,18 +10,18 @@ import {
ResolveField,
Resolver,
} from '@nestjs/graphql';
import type { WorkspacePage as PrismaWorkspacePage } from '@prisma/client';
import type { WorkspaceDoc as PrismaWorkspaceDoc } from '@prisma/client';
import { PrismaClient } from '@prisma/client';
import {
DocAccessDenied,
DocDefaultRoleCanNotBeOwner,
DocIsNotPublic,
ExpectToGrantDocUserRoles,
ExpectToPublishPage,
ExpectToPublishDoc,
ExpectToRevokeDocUserRoles,
ExpectToRevokePublicPage,
ExpectToRevokePublicDoc,
ExpectToUpdateDocUserRole,
PageDefaultRoleCanNotBeOwner,
PageIsNotPublic,
paginate,
Paginated,
PaginationInput,
@@ -36,27 +36,27 @@ import {
fixupDocRole,
mapDocRoleToPermissions,
PermissionService,
PublicPageMode,
PublicDocMode,
} from '../../permission';
import { PublicUserType } from '../../user';
import { DocID } from '../../utils/doc';
import { WorkspaceType } from '../types';
registerEnumType(PublicPageMode, {
name: 'PublicPageMode',
description: 'The mode which the public page default in',
registerEnumType(PublicDocMode, {
name: 'PublicDocMode',
description: 'The mode which the public doc default in',
});
@ObjectType()
class WorkspacePage implements Partial<PrismaWorkspacePage> {
class DocType implements Partial<PrismaWorkspaceDoc> {
@Field(() => String, { name: 'id' })
pageId!: string;
docId!: string;
@Field()
workspaceId!: string;
@Field(() => PublicPageMode)
mode!: PublicPageMode;
@Field(() => PublicDocMode)
mode!: PublicDocMode;
@Field()
public!: boolean;
@@ -105,7 +105,7 @@ class RevokeDocUserRoleInput {
}
@InputType()
class UpdatePageDefaultRoleInput {
class UpdateDocDefaultRoleInput {
@Field(() => String)
docId!: string;
@@ -144,13 +144,7 @@ const DocPermissions = registerObjectType<DocActionPermissions>(
);
@ObjectType()
class DocType {
@Field(() => String)
id!: string;
@Field(() => Boolean)
public!: boolean;
export class DocRolePermissions {
@Field(() => DocRole)
role!: DocRole;
@@ -159,8 +153,207 @@ class DocType {
}
@Resolver(() => WorkspaceType)
export class PagePermissionResolver {
private readonly logger = new Logger(PagePermissionResolver.name);
export class WorkspaceDocResolver {
private readonly logger = new Logger(WorkspaceDocResolver.name);
constructor(
private readonly prisma: PrismaClient,
private readonly permission: PermissionService
) {}
@ResolveField(() => [DocType], {
complexity: 2,
deprecationReason: 'use [WorkspaceType.publicDocs] instead',
})
async publicPages(@Parent() workspace: WorkspaceType) {
return this.publicDocs(workspace);
}
@ResolveField(() => [DocType], {
description: 'Get public docs of a workspace',
complexity: 2,
})
async publicDocs(@Parent() workspace: WorkspaceType) {
return this.prisma.workspaceDoc.findMany({
where: {
workspaceId: workspace.id,
public: true,
},
});
}
@ResolveField(() => DocType, {
description: 'Get public page of a workspace by page id.',
complexity: 2,
nullable: true,
deprecationReason: 'use [WorkspaceType.publicDoc] instead',
})
async publicPage(
@Parent() workspace: WorkspaceType,
@Args('pageId') pageId: string
) {
return this.publicDoc(workspace, pageId);
}
@ResolveField(() => DocType, {
description: 'Get public page of a workspace by page id.',
complexity: 2,
nullable: true,
})
async publicDoc(
@Parent() workspace: WorkspaceType,
@Args('docId') docId: string
) {
return this.prisma.workspaceDoc.findFirst({
where: {
workspaceId: workspace.id,
docId,
public: true,
},
});
}
@ResolveField(() => DocType, {
description: 'Get get with given id',
complexity: 2,
})
async doc(
@Parent() workspace: WorkspaceType,
@Args('docId') docId: string
): Promise<DocType> {
const doc = await this.prisma.workspaceDoc.findFirst({
where: {
workspaceId: workspace.id,
docId,
},
});
return (
doc ?? {
docId,
workspaceId: workspace.id,
public: false,
mode: PublicDocMode.Page,
}
);
}
@Mutation(() => DocType, {
deprecationReason: 'use publishDoc instead',
})
async publishPage(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('pageId') pageId: string,
@Args({
name: 'mode',
type: () => PublicDocMode,
nullable: true,
defaultValue: PublicDocMode.Page,
})
mode: PublicDocMode
) {
return this.publishDoc(user, workspaceId, pageId, mode);
}
@Mutation(() => DocType)
async publishDoc(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('docId') rawDocId: string,
@Args({
name: 'mode',
type: () => PublicDocMode,
nullable: true,
defaultValue: PublicDocMode.Page,
})
mode: PublicDocMode
) {
const docId = new DocID(rawDocId, workspaceId);
if (docId.isWorkspace) {
this.logger.error('Expect to publish doc, but it is a workspace', {
workspaceId,
docId: rawDocId,
});
throw new ExpectToPublishDoc();
}
await this.permission.checkPagePermission(
docId.workspace,
docId.guid,
'Doc.Publish',
user.id
);
this.logger.log('Publish page', {
workspaceId,
docId: rawDocId,
mode,
});
return this.permission.publishPage(docId.workspace, docId.guid, mode);
}
@Mutation(() => DocType, {
deprecationReason: 'use revokePublicDoc instead',
})
async revokePublicPage(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('docId') docId: string
) {
return this.revokePublicDoc(user, workspaceId, docId);
}
@Mutation(() => DocType)
async revokePublicDoc(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('docId') rawDocId: string
) {
const docId = new DocID(rawDocId, workspaceId);
if (docId.isWorkspace) {
this.logger.error('Expect to revoke public doc, but it is a workspace', {
workspaceId,
docId: rawDocId,
});
throw new ExpectToRevokePublicDoc('Expect doc not to be workspace');
}
await this.permission.checkPagePermission(
docId.workspace,
docId.guid,
'Doc.Publish',
user.id
);
const isPublic = await this.permission.isPublicPage(
docId.workspace,
docId.guid
);
if (!isPublic) {
this.logger.log('Expect to revoke public doc, but it is not public', {
workspaceId,
docId: rawDocId,
});
throw new DocIsNotPublic('Doc is not public');
}
this.logger.log('Revoke public doc', {
workspaceId,
docId: rawDocId,
});
return this.permission.revokePublicPage(docId.workspace, docId.guid);
}
}
@Resolver(() => DocType)
export class DocResolver {
private readonly logger = new Logger(DocResolver.name);
constructor(
private readonly prisma: PrismaClient,
@@ -168,96 +361,31 @@ export class PagePermissionResolver {
private readonly models: Models
) {}
/**
* @deprecated
*/
@ResolveField(() => [String], {
description: 'Shared pages of workspace',
complexity: 2,
deprecationReason: 'use WorkspaceType.publicPages',
})
async sharedPages(@Parent() workspace: WorkspaceType) {
const data = await this.prisma.workspacePage.findMany({
where: {
workspaceId: workspace.id,
public: true,
},
});
return data.map(row => row.pageId);
}
@ResolveField(() => [WorkspacePage], {
description: 'Public pages of a workspace',
complexity: 2,
})
async publicPages(@Parent() workspace: WorkspaceType) {
return this.prisma.workspacePage.findMany({
where: {
workspaceId: workspace.id,
public: true,
},
});
}
@ResolveField(() => WorkspacePage, {
description: 'Get public page of a workspace by page id.',
complexity: 2,
nullable: true,
})
async publicPage(
@Parent() workspace: WorkspaceType,
@Args('pageId') pageId: string
) {
return this.prisma.workspacePage.findFirst({
where: {
workspaceId: workspace.id,
pageId,
public: true,
},
});
}
@ResolveField(() => DocType, {
description: 'Check if current user has permission to access the page',
complexity: 2,
})
async pagePermission(
@Parent() workspace: WorkspaceType,
@Args('pageId') pageId: string,
@CurrentUser() user: CurrentUser
): Promise<DocType> {
const page = await this.prisma.workspacePage.findFirst({
where: {
workspaceId: workspace.id,
pageId,
},
select: {
public: true,
},
});
@ResolveField(() => DocPermissions)
async permissions(
@CurrentUser() user: CurrentUser,
@Parent() doc: DocType
): Promise<DocRolePermissions> {
const [permission, workspacePermission] = await this.prisma.$transaction(
tx =>
Promise.all([
tx.workspacePageUserPermission.findFirst({
tx.workspaceDocUserPermission.findFirst({
where: {
workspaceId: workspace.id,
pageId,
workspaceId: doc.workspaceId,
docId: doc.docId,
userId: user.id,
},
}),
tx.workspaceUserPermission.findFirst({
where: {
workspaceId: workspace.id,
workspaceId: doc.workspaceId,
userId: user.id,
},
}),
])
);
return {
id: pageId,
public: page?.public ?? false,
role: permission?.type ?? DocRole.External,
permissions: mapDocRoleToPermissions(
fixupDocRole(workspacePermission?.type, permission?.type)
@@ -266,29 +394,27 @@ export class PagePermissionResolver {
}
@ResolveField(() => PaginatedGrantedDocUserType, {
description: 'Page granted users list',
description: 'paginated doc granted users list',
complexity: 4,
})
async pageGrantedUsersList(
async grantedUsersList(
@CurrentUser() user: CurrentUser,
@Parent() workspace: WorkspaceType,
@Args('pageId') pageId: string,
@Parent() doc: DocType,
@Args('pagination') pagination: PaginationInput
): Promise<PaginatedGrantedDocUserType> {
await this.permission.checkPagePermission(
workspace.id,
pageId,
doc.workspaceId,
doc.docId,
'Doc.Users.Read',
user.id
);
const docId = new DocID(pageId, workspace.id);
const [permissions, totalCount] = await this.prisma.$transaction(tx => {
return Promise.all([
tx.workspacePageUserPermission.findMany({
tx.workspaceDocUserPermission.findMany({
where: {
workspaceId: workspace.id,
pageId: docId.guid,
workspaceId: doc.workspaceId,
docId: doc.docId,
createdAt: pagination.after
? {
gt: pagination.after,
@@ -306,10 +432,10 @@ export class PagePermissionResolver {
take: pagination.first,
skip: pagination.offset,
}),
tx.workspacePageUserPermission.count({
tx.workspaceDocUserPermission.count({
where: {
workspaceId: workspace.id,
pageId: docId.guid,
workspaceId: doc.workspaceId,
docId: doc.docId,
},
}),
]);
@@ -338,121 +464,6 @@ export class PagePermissionResolver {
);
}
/**
* @deprecated
*/
@Mutation(() => Boolean, {
name: 'sharePage',
deprecationReason: 'renamed to publishPage',
})
async deprecatedSharePage(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('pageId') pageId: string
) {
await this.publishPage(user, workspaceId, pageId, PublicPageMode.Page);
return true;
}
@Mutation(() => WorkspacePage)
async publishPage(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('pageId') pageId: string,
@Args({
name: 'mode',
type: () => PublicPageMode,
nullable: true,
defaultValue: PublicPageMode.Page,
})
mode: PublicPageMode
) {
const docId = new DocID(pageId, workspaceId);
if (docId.isWorkspace) {
this.logger.error('Expect to publish page, but it is a workspace', {
workspaceId,
pageId,
});
throw new ExpectToPublishPage();
}
await this.permission.checkPagePermission(
docId.workspace,
docId.guid,
'Doc.Publish',
user.id
);
this.logger.log('Publish page', {
workspaceId,
pageId,
mode,
});
return this.permission.publishPage(docId.workspace, docId.guid, mode);
}
/**
* @deprecated
*/
@Mutation(() => Boolean, {
name: 'revokePage',
deprecationReason: 'use revokePublicPage',
})
async deprecatedRevokePage(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('pageId') pageId: string
) {
await this.revokePublicPage(user, workspaceId, pageId);
return true;
}
@Mutation(() => WorkspacePage)
async revokePublicPage(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('pageId') pageId: string
) {
const docId = new DocID(pageId, workspaceId);
if (docId.isWorkspace) {
this.logger.error('Expect to revoke public page, but it is a workspace', {
workspaceId,
pageId,
});
throw new ExpectToRevokePublicPage('Expect page not to be workspace');
}
await this.permission.checkPagePermission(
docId.workspace,
docId.guid,
'Doc.Publish',
user.id
);
const isPublic = await this.permission.isPublicPage(
docId.workspace,
docId.guid
);
if (!isPublic) {
this.logger.log('Expect to revoke public page, but it is not public', {
workspaceId,
pageId,
});
throw new PageIsNotPublic('Page is not public');
}
this.logger.log('Revoke public page', {
workspaceId,
pageId,
});
return this.permission.revokePublicPage(docId.workspace, docId.guid);
}
@Mutation(() => Boolean)
async grantDocUserRoles(
@CurrentUser() user: CurrentUser,
@@ -580,13 +591,13 @@ export class PagePermissionResolver {
}
@Mutation(() => Boolean)
async updatePageDefaultRole(
async updateDocDefaultRole(
@CurrentUser() user: CurrentUser,
@Args('input') input: UpdatePageDefaultRoleInput
@Args('input') input: UpdateDocDefaultRoleInput
) {
if (input.role === DocRole.Owner) {
this.logger.log('Page default role can not be owner', input);
throw new PageDefaultRoleCanNotBeOwner();
this.logger.log('Doc default role can not be owner', input);
throw new DocDefaultRoleCanNotBeOwner();
}
const doc = new DocID(input.docId, input.workspaceId);
const pairs = {
@@ -622,11 +633,11 @@ export class PagePermissionResolver {
}
throw error;
}
await this.prisma.workspacePage.upsert({
await this.prisma.workspaceDoc.upsert({
where: {
workspaceId_pageId: {
workspaceId_docId: {
workspaceId: doc.workspace,
pageId: doc.guid,
docId: doc.guid,
},
},
update: {
@@ -634,7 +645,7 @@ export class PagePermissionResolver {
},
create: {
workspaceId: doc.workspace,
pageId: doc.guid,
docId: doc.guid,
defaultRole: input.role,
},
});

View File

@@ -1,6 +1,6 @@
export * from './blob';
export * from './doc';
export * from './history';
export * from './page';
export * from './service';
export * from './team';
export * from './workspace';

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { Transactional } from '@nestjs-cls/transactional';
import {
type WorkspacePage as Page,
type WorkspacePageUserPermission as PageUserPermission,
type WorkspaceDoc as Page,
type WorkspaceDocUserPermission as PageUserPermission,
} from '@prisma/client';
import { WorkspaceRole } from '../core/permission';
@@ -21,12 +21,12 @@ export class PageModel extends BaseModel {
/**
* Create or update the page.
*/
async upsert(workspaceId: string, pageId: string, data?: UpdatePageInput) {
return await this.db.workspacePage.upsert({
async upsert(workspaceId: string, docId: string, data?: UpdatePageInput) {
return await this.db.workspaceDoc.upsert({
where: {
workspaceId_pageId: {
workspaceId_docId: {
workspaceId,
pageId,
docId,
},
},
update: {
@@ -35,7 +35,7 @@ export class PageModel extends BaseModel {
create: {
...data,
workspaceId,
pageId,
docId,
},
});
}
@@ -45,12 +45,12 @@ export class PageModel extends BaseModel {
* @param isPublic: if true, only return the public page. If false, only return the private page.
* If not set, return public or private both.
*/
async get(workspaceId: string, pageId: string, isPublic?: boolean) {
return await this.db.workspacePage.findUnique({
async get(workspaceId: string, docId: string, isPublic?: boolean) {
return await this.db.workspaceDoc.findUnique({
where: {
workspaceId_pageId: {
workspaceId_docId: {
workspaceId,
pageId,
docId,
},
public: isPublic,
},
@@ -61,7 +61,7 @@ export class PageModel extends BaseModel {
* Find the workspace public pages.
*/
async findPublics(workspaceId: string) {
return await this.db.workspacePage.findMany({
return await this.db.workspaceDoc.findMany({
where: {
workspaceId,
public: true,
@@ -73,7 +73,7 @@ export class PageModel extends BaseModel {
* Get the workspace public pages count.
*/
async getPublicsCount(workspaceId: string) {
return await this.db.workspacePage.count({
return await this.db.workspaceDoc.count({
where: {
workspaceId,
public: true,
@@ -91,15 +91,15 @@ export class PageModel extends BaseModel {
@Transactional()
async grantMember(
workspaceId: string,
pageId: string,
docId: string,
userId: string,
permission: WorkspaceRole = WorkspaceRole.Collaborator
): Promise<PageUserPermission> {
let data = await this.db.workspacePageUserPermission.findUnique({
let data = await this.db.workspaceDocUserPermission.findUnique({
where: {
workspaceId_pageId_userId: {
workspaceId_docId_userId: {
workspaceId,
pageId,
docId,
userId,
},
},
@@ -109,11 +109,11 @@ export class PageModel extends BaseModel {
if (!data || data.type !== permission) {
if (data) {
// Update the permission
data = await this.db.workspacePageUserPermission.update({
data = await this.db.workspaceDocUserPermission.update({
where: {
workspaceId_pageId_userId: {
workspaceId_docId_userId: {
workspaceId,
pageId,
docId,
userId,
},
},
@@ -121,10 +121,10 @@ export class PageModel extends BaseModel {
});
} else {
// Create a new permission
data = await this.db.workspacePageUserPermission.create({
data = await this.db.workspaceDocUserPermission.create({
data: {
workspaceId,
pageId,
docId,
userId,
type: permission,
},
@@ -133,17 +133,17 @@ export class PageModel extends BaseModel {
// If the new permission is owner, we need to revoke old owner
if (permission === WorkspaceRole.Owner) {
await this.db.workspacePageUserPermission.updateMany({
await this.db.workspaceDocUserPermission.updateMany({
where: {
workspaceId,
pageId,
docId,
type: WorkspaceRole.Owner,
userId: { not: userId },
},
data: { type: WorkspaceRole.Admin },
});
this.logger.log(
`Change owner of workspace ${workspaceId} page ${pageId} to user ${userId}`
`Change owner of workspace ${workspaceId} doc ${docId} to user ${userId}`
);
}
return data;
@@ -159,14 +159,14 @@ export class PageModel extends BaseModel {
*/
async isMember(
workspaceId: string,
pageId: string,
docId: string,
userId: string,
permission: WorkspaceRole = WorkspaceRole.Collaborator
) {
const count = await this.db.workspacePageUserPermission.count({
const count = await this.db.workspaceDocUserPermission.count({
where: {
workspaceId,
pageId,
docId,
userId,
type: {
gte: permission,
@@ -180,11 +180,11 @@ export class PageModel extends BaseModel {
* Delete a page member
* Except the owner, the owner can't be deleted.
*/
async deleteMember(workspaceId: string, pageId: string, userId: string) {
const { count } = await this.db.workspacePageUserPermission.deleteMany({
async deleteMember(workspaceId: string, docId: string, userId: string) {
const { count } = await this.db.workspaceDocUserPermission.deleteMany({
where: {
workspaceId,
pageId,
docId,
userId,
type: {
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading

View File

@@ -230,10 +230,13 @@ enum DocRole {
}
type DocType {
"""paginated doc granted users list"""
grantedUsersList(pagination: PaginationInput!): PaginatedGrantedDocUserType!
id: String!
mode: PublicDocMode!
permissions: DocPermissions!
public: Boolean!
role: DocRole!
workspaceId: String!
}
type EditorType {
@@ -254,7 +257,7 @@ enum ErrorNames {
CANNOT_DELETE_ALL_ADMIN_ACCOUNT
CANNOT_DELETE_OWN_ACCOUNT
CANT_UPDATE_ONETIME_PAYMENT_SUBSCRIPTION
CAN_NOT_BATCH_GRANT_PAGE_OWNER_PERMISSIONS
CAN_NOT_BATCH_GRANT_DOC_OWNER_PERMISSIONS
CAPTCHA_VERIFICATION_FAILED
COPILOT_ACTION_TAKEN
COPILOT_FAILED_TO_CREATE_MESSAGE
@@ -268,16 +271,18 @@ enum ErrorNames {
COPILOT_SESSION_NOT_FOUND
CUSTOMER_PORTAL_CREATE_FAILED
DOC_ACCESS_DENIED
DOC_DEFAULT_ROLE_CAN_NOT_BE_OWNER
DOC_HISTORY_NOT_FOUND
DOC_IS_NOT_PUBLIC
DOC_NOT_FOUND
EARLY_ACCESS_REQUIRED
EMAIL_ALREADY_USED
EMAIL_TOKEN_NOT_FOUND
EMAIL_VERIFICATION_REQUIRED
EXPECT_TO_GRANT_DOC_USER_ROLES
EXPECT_TO_PUBLISH_PAGE
EXPECT_TO_PUBLISH_DOC
EXPECT_TO_REVOKE_DOC_USER_ROLES
EXPECT_TO_REVOKE_PUBLIC_PAGE
EXPECT_TO_REVOKE_PUBLIC_DOC
EXPECT_TO_UPDATE_DOC_USER_ROLE
FAILED_TO_CHECKOUT
FAILED_TO_SAVE_UPDATES
@@ -306,8 +311,6 @@ enum ErrorNames {
NO_COPILOT_PROVIDER_AVAILABLE
OAUTH_ACCOUNT_ALREADY_CONNECTED
OAUTH_STATE_EXPIRED
PAGE_DEFAULT_ROLE_CAN_NOT_BE_OWNER
PAGE_IS_NOT_PUBLIC
PASSWORD_REQUIRED
QUERY_TOO_LONG
RUNTIME_CONFIG_NOT_FOUND
@@ -621,7 +624,8 @@ type Mutation {
invite(email: String!, permission: Permission @deprecated(reason: "never used"), sendInviteMail: Boolean, workspaceId: String!): String!
inviteBatch(emails: [String!]!, sendInviteMail: Boolean, workspaceId: String!): [InviteResult!]!
leaveWorkspace(sendLeaveMail: Boolean, workspaceId: String!, workspaceName: String @deprecated(reason: "no longer used")): Boolean!
publishPage(mode: PublicPageMode = Page, pageId: String!, workspaceId: String!): WorkspacePage!
publishDoc(docId: String!, mode: PublicDocMode = Page, workspaceId: String!): DocType!
publishPage(mode: PublicDocMode = Page, pageId: String!, workspaceId: String!): DocType! @deprecated(reason: "use publishDoc instead")
recoverDoc(guid: String!, timestamp: DateTime!, workspaceId: String!): DateTime!
releaseDeletedBlobs(workspaceId: String!): Boolean!
@@ -632,23 +636,22 @@ type Mutation {
revoke(userId: String!, workspaceId: String!): Boolean!
revokeDocUserRoles(input: RevokeDocUserRoleInput!): Boolean!
revokeInviteLink(workspaceId: String!): Boolean!
revokePage(pageId: String!, workspaceId: String!): Boolean! @deprecated(reason: "use revokePublicPage")
revokePublicPage(pageId: String!, workspaceId: String!): WorkspacePage!
revokePublicDoc(docId: String!, workspaceId: String!): DocType!
revokePublicPage(docId: String!, workspaceId: String!): DocType! @deprecated(reason: "use revokePublicDoc instead")
sendChangeEmail(callbackUrl: String!, email: String): Boolean!
sendChangePasswordEmail(callbackUrl: String!, email: String @deprecated(reason: "fetched from signed in user")): Boolean!
sendSetPasswordEmail(callbackUrl: String!, email: String @deprecated(reason: "fetched from signed in user")): Boolean!
sendVerifyChangeEmail(callbackUrl: String!, email: String!, token: String!): Boolean!
sendVerifyEmail(callbackUrl: String!): Boolean!
setBlob(blob: Upload!, workspaceId: String!): String!
sharePage(pageId: String!, workspaceId: String!): Boolean! @deprecated(reason: "renamed to publishPage")
"""Update a copilot prompt"""
updateCopilotPrompt(messages: [CopilotPromptMessageInput!]!, name: String!): CopilotPromptType!
"""Update a chat session"""
updateCopilotSession(options: UpdateChatSessionInput!): String!
updateDocDefaultRole(input: UpdateDocDefaultRoleInput!): Boolean!
updateDocUserRole(input: UpdateDocUserRoleInput!): Boolean!
updatePageDefaultRole(input: UpdatePageDefaultRoleInput!): Boolean!
updateProfile(input: UpdateUserInput!): UserType!
"""update server runtime configurable setting"""
@@ -719,8 +722,8 @@ enum Permission {
Owner
}
"""The mode which the public page default in"""
enum PublicPageMode {
"""The mode which the public doc default in"""
enum PublicDocMode {
Edgeless
Page
}
@@ -1005,6 +1008,12 @@ input UpdateChatSessionInput {
sessionId: String!
}
input UpdateDocDefaultRoleInput {
docId: String!
role: DocRole!
workspaceId: String!
}
input UpdateDocUserRoleInput {
docId: String!
role: DocRole!
@@ -1012,12 +1021,6 @@ input UpdateDocUserRoleInput {
workspaceId: String!
}
input UpdatePageDefaultRoleInput {
docId: String!
role: DocRole!
workspaceId: String!
}
input UpdateUserInput {
"""User name"""
name: String
@@ -1128,13 +1131,6 @@ type WorkspaceMembersExceedLimitToDowngradeDataType {
limit: Int!
}
type WorkspacePage {
id: String!
mode: PublicPageMode!
public: Boolean!
workspaceId: String!
}
type WorkspacePageMeta {
createdAt: DateTime!
createdBy: EditorType
@@ -1199,6 +1195,9 @@ type WorkspaceType {
"""Workspace created date"""
createdAt: DateTime!
"""Get get with given id"""
doc(docId: String!): DocType!
"""Enable AI"""
enableAi: Boolean!
@@ -1229,23 +1228,21 @@ type WorkspaceType {
"""Owner of workspace"""
owner: UserType!
"""Page granted users list"""
pageGrantedUsersList(pageId: String!, pagination: PaginationInput!): PaginatedGrantedDocUserType!
"""Cloud page metadata of workspace"""
pageMeta(pageId: String!): WorkspacePageMeta!
"""Check if current user has permission to access the page"""
pagePermission(pageId: String!): DocType!
"""is Public workspace"""
public: Boolean!
"""Get public page of a workspace by page id."""
publicPage(pageId: String!): WorkspacePage
publicDoc(docId: String!): DocType
"""Public pages of a workspace"""
publicPages: [WorkspacePage!]!
"""Get public docs of a workspace"""
publicDocs: [DocType!]!
"""Get public page of a workspace by page id."""
publicPage(pageId: String!): DocType @deprecated(reason: "use [WorkspaceType.publicDoc] instead")
publicPages: [DocType!]! @deprecated(reason: "use [WorkspaceType.publicDocs] instead")
"""quota of workspace"""
quota: WorkspaceQuotaType!
@@ -1253,9 +1250,6 @@ type WorkspaceType {
"""Role of current signed in user in workspace"""
role: Permission!
"""Shared pages of workspace"""
sharedPages: [String!]! @deprecated(reason: "use WorkspaceType.publicPages")
"""The team subscription of the workspace, if exists."""
subscription: SubscriptionType