mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 18:02:47 +08:00
feat(server): doc level permission (#9760)
close CLOUD-89 CLOUD-90 CLOUD-91 CLOUD-92
This commit is contained in:
@@ -141,7 +141,7 @@ model WorkspaceUserPermission {
|
|||||||
id String @id @default(uuid()) @db.VarChar
|
id String @id @default(uuid()) @db.VarChar
|
||||||
workspaceId String @map("workspace_id") @db.VarChar
|
workspaceId String @map("workspace_id") @db.VarChar
|
||||||
userId String @map("user_id") @db.VarChar
|
userId String @map("user_id") @db.VarChar
|
||||||
// Read/Write
|
// Workspace Role, Owner/Admin/Collaborator/External
|
||||||
type Int @db.SmallInt
|
type Int @db.SmallInt
|
||||||
/// @deprecated Whether the permission invitation is accepted by the user
|
/// @deprecated Whether the permission invitation is accepted by the user
|
||||||
accepted Boolean @default(false)
|
accepted Boolean @default(false)
|
||||||
@@ -165,7 +165,7 @@ model WorkspacePageUserPermission {
|
|||||||
workspaceId String @map("workspace_id") @db.VarChar
|
workspaceId String @map("workspace_id") @db.VarChar
|
||||||
pageId String @map("page_id") @db.VarChar
|
pageId String @map("page_id") @db.VarChar
|
||||||
userId String @map("user_id") @db.VarChar
|
userId String @map("user_id") @db.VarChar
|
||||||
// Read/Write
|
// External/Reader/Editor/Manager/Owner
|
||||||
type Int @db.SmallInt
|
type Int @db.SmallInt
|
||||||
/// Whether the permission invitation is accepted by the user
|
/// Whether the permission invitation is accepted by the user
|
||||||
accepted Boolean @default(false)
|
accepted Boolean @default(false)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { PrismaClient } from '@prisma/client';
|
|||||||
import ava, { TestFn } from 'ava';
|
import ava, { TestFn } from 'ava';
|
||||||
|
|
||||||
import { Config } from '../../base/config';
|
import { Config } from '../../base/config';
|
||||||
import { Permission, PublicPageMode } from '../../models/common';
|
import { WorkspaceRole } from '../../core/permission';
|
||||||
|
import { PublicPageMode } from '../../models/common';
|
||||||
import { PageModel } from '../../models/page';
|
import { PageModel } from '../../models/page';
|
||||||
import { type User, UserModel } from '../../models/user';
|
import { type User, UserModel } from '../../models/user';
|
||||||
import { type Workspace, WorkspaceModel } from '../../models/workspace';
|
import { type Workspace, WorkspaceModel } from '../../models/workspace';
|
||||||
@@ -131,7 +132,7 @@ test('should grant a member to access a page', async t => {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.false(hasAccess);
|
t.false(hasAccess);
|
||||||
// grant write permission
|
// grant write permission
|
||||||
@@ -139,20 +140,20 @@ test('should grant a member to access a page', async t => {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
hasAccess = await t.context.page.isMember(
|
hasAccess = await t.context.page.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.true(hasAccess);
|
t.true(hasAccess);
|
||||||
hasAccess = await t.context.page.isMember(
|
hasAccess = await t.context.page.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Read
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.true(hasAccess);
|
t.true(hasAccess);
|
||||||
// delete member
|
// delete member
|
||||||
@@ -174,14 +175,14 @@ test('should change the page owner', async t => {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
t.true(
|
t.true(
|
||||||
await t.context.page.isMember(
|
await t.context.page.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -193,14 +194,14 @@ test('should change the page owner', async t => {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
t.true(
|
t.true(
|
||||||
await t.context.page.isMember(
|
await t.context.page.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
t.false(
|
t.false(
|
||||||
@@ -208,7 +209,7 @@ test('should change the page owner', async t => {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -221,7 +222,7 @@ test('should not delete owner from page', async t => {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
'page1',
|
'page1',
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
const count = await t.context.page.deleteMember(
|
const count = await t.context.page.deleteMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import ava, { TestFn } from 'ava';
|
|||||||
import Sinon from 'sinon';
|
import Sinon from 'sinon';
|
||||||
|
|
||||||
import { EmailAlreadyUsed, EventBus } from '../../base';
|
import { EmailAlreadyUsed, EventBus } from '../../base';
|
||||||
import { Permission } from '../../models/common';
|
import { WorkspaceRole } from '../../core/permission';
|
||||||
import { UserModel } from '../../models/user';
|
import { UserModel } from '../../models/user';
|
||||||
import { WorkspaceMemberStatus } from '../../models/workspace';
|
import { WorkspaceMemberStatus } from '../../models/workspace';
|
||||||
import { createTestingModule, initTestingDB } from '../utils';
|
import { createTestingModule, initTestingDB } from '../utils';
|
||||||
@@ -263,7 +263,7 @@ test('should trigger user.deleted event', async t => {
|
|||||||
public: false,
|
public: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
status: WorkspaceMemberStatus.Accepted,
|
status: WorkspaceMemberStatus.Accepted,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import ava, { TestFn } from 'ava';
|
|||||||
import Sinon from 'sinon';
|
import Sinon from 'sinon';
|
||||||
|
|
||||||
import { Config, EventBus } from '../../base';
|
import { Config, EventBus } from '../../base';
|
||||||
import { Permission } from '../../models/common';
|
import { WorkspaceRole } from '../../core/permission';
|
||||||
import { UserModel } from '../../models/user';
|
import { UserModel } from '../../models/user';
|
||||||
import { WorkspaceModel } from '../../models/workspace';
|
import { WorkspaceModel } from '../../models/workspace';
|
||||||
import { createTestingModule, initTestingDB } from '../utils';
|
import { createTestingModule, initTestingDB } from '../utils';
|
||||||
@@ -92,25 +92,25 @@ test('should workspace owner has all permissions', async t => {
|
|||||||
let allowed = await t.context.workspace.isMember(
|
let allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Read
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
});
|
});
|
||||||
@@ -127,32 +127,32 @@ test('should workspace admin has all permissions except owner', async t => {
|
|||||||
data: {
|
data: {
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
userId: otherUser.id,
|
userId: otherUser.id,
|
||||||
type: Permission.Admin,
|
type: WorkspaceRole.Admin,
|
||||||
status: WorkspaceMemberStatus.Accepted,
|
status: WorkspaceMemberStatus.Accepted,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let allowed = await t.context.workspace.isMember(
|
let allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
});
|
});
|
||||||
@@ -169,32 +169,32 @@ test('should workspace write has write and read permissions', async t => {
|
|||||||
data: {
|
data: {
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
userId: otherUser.id,
|
userId: otherUser.id,
|
||||||
type: Permission.Write,
|
type: WorkspaceRole.Collaborator,
|
||||||
status: WorkspaceMemberStatus.Accepted,
|
status: WorkspaceMemberStatus.Accepted,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let allowed = await t.context.workspace.isMember(
|
let allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
});
|
});
|
||||||
@@ -211,32 +211,26 @@ test('should workspace read has read permission only', async t => {
|
|||||||
data: {
|
data: {
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
userId: otherUser.id,
|
userId: otherUser.id,
|
||||||
type: Permission.Read,
|
type: WorkspaceRole.Collaborator,
|
||||||
status: WorkspaceMemberStatus.Accepted,
|
status: WorkspaceMemberStatus.Accepted,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let allowed = await t.context.workspace.isMember(
|
let allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
|
||||||
t.is(allowed, false);
|
|
||||||
allowed = await t.context.workspace.isMember(
|
|
||||||
workspace.id,
|
|
||||||
otherUser.id,
|
|
||||||
Permission.Read
|
|
||||||
);
|
);
|
||||||
t.is(allowed, true);
|
t.is(allowed, true);
|
||||||
});
|
});
|
||||||
@@ -252,25 +246,25 @@ test('should user not in workspace has no permissions', async t => {
|
|||||||
let allowed = await t.context.workspace.isMember(
|
let allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
allowed = await t.context.workspace.isMember(
|
allowed = await t.context.workspace.isMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
t.is(allowed, false);
|
t.is(allowed, false);
|
||||||
});
|
});
|
||||||
@@ -313,7 +307,7 @@ test('should grant member with read permission and Pending status by default', a
|
|||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.Pending);
|
t.is(member1.status, WorkspaceMemberStatus.Pending);
|
||||||
|
|
||||||
// grant again should do nothing
|
// grant again should do nothing
|
||||||
@@ -344,18 +338,18 @@ test('should grant Pending status member to Accepted status', async t => {
|
|||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.Pending);
|
t.is(member1.status, WorkspaceMemberStatus.Pending);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
t.is(member2.type, Permission.Read);
|
t.is(member2.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -370,27 +364,27 @@ test('should grant new owner and change exists owner to admin', async t => {
|
|||||||
const member1 = await t.context.workspace.grantMember(
|
const member1 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.Accepted);
|
t.is(member1.status, WorkspaceMemberStatus.Accepted);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Owner,
|
WorkspaceRole.Owner,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
t.is(member2.type, Permission.Owner);
|
t.is(member2.type, WorkspaceRole.Owner);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
||||||
// check old owner
|
// check old owner
|
||||||
const owner = await t.context.workspace.getMember(workspace.id, user.id);
|
const owner = await t.context.workspace.getMember(workspace.id, user.id);
|
||||||
t.is(owner!.type, Permission.Admin);
|
t.is(owner!.type, WorkspaceRole.Admin);
|
||||||
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
|
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -405,23 +399,23 @@ test('should grant write permission on exists member', async t => {
|
|||||||
const member1 = await t.context.workspace.grantMember(
|
const member1 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.Accepted);
|
t.is(member1.status, WorkspaceMemberStatus.Accepted);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Write,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
t.is(member2.type, Permission.Write);
|
t.is(member2.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -436,23 +430,23 @@ test('should grant UnderReview status member to Accepted status', async t => {
|
|||||||
const member1 = await t.context.workspace.grantMember(
|
const member1 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.UnderReview
|
WorkspaceMemberStatus.UnderReview
|
||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.UnderReview);
|
t.is(member1.status, WorkspaceMemberStatus.UnderReview);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
t.is(member2.type, Permission.Read);
|
t.is(member2.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -467,23 +461,23 @@ test('should grant NeedMoreSeat status member to Pending status', async t => {
|
|||||||
const member1 = await t.context.workspace.grantMember(
|
const member1 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.NeedMoreSeat
|
WorkspaceMemberStatus.NeedMoreSeat
|
||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
|
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Pending
|
WorkspaceMemberStatus.Pending
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
t.is(member2.type, Permission.Read);
|
t.is(member2.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.Pending);
|
t.is(member2.status, WorkspaceMemberStatus.Pending);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -498,23 +492,23 @@ test('should grant NeedMoreSeatAndReview status member to UnderReview status', a
|
|||||||
const member1 = await t.context.workspace.grantMember(
|
const member1 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
|
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.UnderReview
|
WorkspaceMemberStatus.UnderReview
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
t.is(member2.type, Permission.Read);
|
t.is(member2.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.UnderReview);
|
t.is(member2.status, WorkspaceMemberStatus.UnderReview);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -532,19 +526,19 @@ test('should grant Pending status member to write permission and Accepted status
|
|||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.Pending);
|
t.is(member1.status, WorkspaceMemberStatus.Pending);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Write,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
// TODO(fengmk2): fix this
|
// TODO(fengmk2): fix this
|
||||||
// t.is(member2.type, Permission.Write);
|
// t.is(member2.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -559,23 +553,23 @@ test('should grant no thing on invalid status', async t => {
|
|||||||
const member1 = await t.context.workspace.grantMember(
|
const member1 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.NeedMoreSeat
|
WorkspaceMemberStatus.NeedMoreSeat
|
||||||
);
|
);
|
||||||
t.is(member1.workspaceId, workspace.id);
|
t.is(member1.workspaceId, workspace.id);
|
||||||
t.is(member1.userId, otherUser.id);
|
t.is(member1.userId, otherUser.id);
|
||||||
t.is(member1.type, Permission.Read);
|
t.is(member1.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
|
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
|
||||||
|
|
||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member2.workspaceId, workspace.id);
|
t.is(member2.workspaceId, workspace.id);
|
||||||
t.is(member2.userId, otherUser.id);
|
t.is(member2.userId, otherUser.id);
|
||||||
t.is(member2.type, Permission.Read);
|
t.is(member2.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.NeedMoreSeat);
|
t.is(member2.status, WorkspaceMemberStatus.NeedMoreSeat);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -590,7 +584,7 @@ test('should get the accepted status workspace member', async t => {
|
|||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
const member = await t.context.workspace.getMember(
|
const member = await t.context.workspace.getMember(
|
||||||
@@ -599,7 +593,7 @@ test('should get the accepted status workspace member', async t => {
|
|||||||
);
|
);
|
||||||
t.is(member!.workspaceId, workspace.id);
|
t.is(member!.workspaceId, workspace.id);
|
||||||
t.is(member!.userId, otherUser.id);
|
t.is(member!.userId, otherUser.id);
|
||||||
t.is(member!.type, Permission.Read);
|
t.is(member!.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member!.status, WorkspaceMemberStatus.Accepted);
|
t.is(member!.status, WorkspaceMemberStatus.Accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -614,7 +608,7 @@ test('should get any status workspace member, including pending and accepted', a
|
|||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Pending
|
WorkspaceMemberStatus.Pending
|
||||||
);
|
);
|
||||||
const member = await t.context.workspace.getMemberInAnyStatus(
|
const member = await t.context.workspace.getMemberInAnyStatus(
|
||||||
@@ -623,7 +617,7 @@ test('should get any status workspace member, including pending and accepted', a
|
|||||||
);
|
);
|
||||||
t.is(member!.workspaceId, workspace.id);
|
t.is(member!.workspaceId, workspace.id);
|
||||||
t.is(member!.userId, otherUser.id);
|
t.is(member!.userId, otherUser.id);
|
||||||
t.is(member!.type, Permission.Read);
|
t.is(member!.type, WorkspaceRole.Collaborator);
|
||||||
t.is(member!.status, WorkspaceMemberStatus.Pending);
|
t.is(member!.status, WorkspaceMemberStatus.Pending);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -635,7 +629,7 @@ test('should get workspace owner by workspace id', async t => {
|
|||||||
const owner = await t.context.workspace.getOwner(workspace.id);
|
const owner = await t.context.workspace.getOwner(workspace.id);
|
||||||
t.is(owner!.workspaceId, workspace.id);
|
t.is(owner!.workspaceId, workspace.id);
|
||||||
t.is(owner!.userId, user.id);
|
t.is(owner!.userId, user.id);
|
||||||
t.is(owner!.type, Permission.Owner);
|
t.is(owner!.type, WorkspaceRole.Owner);
|
||||||
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
|
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
|
||||||
t.truthy(owner!.user);
|
t.truthy(owner!.user);
|
||||||
t.deepEqual(owner!.user, user);
|
t.deepEqual(owner!.user, user);
|
||||||
@@ -658,27 +652,27 @@ test('should find workspace admin by workspace id', async t => {
|
|||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser1.id,
|
otherUser1.id,
|
||||||
Permission.Admin,
|
WorkspaceRole.Admin,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser2.id,
|
otherUser2.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
// pending member should not be admin
|
// pending member should not be admin
|
||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser3.id,
|
otherUser3.id,
|
||||||
Permission.Admin,
|
WorkspaceRole.Admin,
|
||||||
WorkspaceMemberStatus.Pending
|
WorkspaceMemberStatus.Pending
|
||||||
);
|
);
|
||||||
const members = await t.context.workspace.findAdmins(workspace.id);
|
const members = await t.context.workspace.findAdmins(workspace.id);
|
||||||
t.is(members.length, 1);
|
t.is(members.length, 1);
|
||||||
t.is(members[0].workspaceId, workspace.id);
|
t.is(members[0].workspaceId, workspace.id);
|
||||||
t.is(members[0].userId, otherUser1.id);
|
t.is(members[0].userId, otherUser1.id);
|
||||||
t.is(members[0].type, Permission.Admin);
|
t.is(members[0].type, WorkspaceRole.Admin);
|
||||||
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
|
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -710,13 +704,13 @@ test('should the workspace member total count, including pending and accepted',
|
|||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser1.id,
|
otherUser1.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Pending
|
WorkspaceMemberStatus.Pending
|
||||||
);
|
);
|
||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser2.id,
|
otherUser2.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
const count = await t.context.workspace.getMemberTotalCount(workspace.id);
|
const count = await t.context.workspace.getMemberTotalCount(workspace.id);
|
||||||
@@ -737,13 +731,13 @@ test('should the workspace member used count, only count the accepted member', a
|
|||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser1.id,
|
otherUser1.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Pending
|
WorkspaceMemberStatus.Pending
|
||||||
);
|
);
|
||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser2.id,
|
otherUser2.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
const count = await t.context.workspace.getMemberUsedCount(workspace.id);
|
const count = await t.context.workspace.getMemberUsedCount(workspace.id);
|
||||||
@@ -855,7 +849,7 @@ test('should delete workspace member in Pending, Accepted status', async t => {
|
|||||||
const member2 = await t.context.workspace.grantMember(
|
const member2 = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
t.is(member2.status, WorkspaceMemberStatus.Accepted);
|
||||||
@@ -874,7 +868,7 @@ test('should trigger workspace.members.requestDeclined event when delete workspa
|
|||||||
const member = await t.context.workspace.grantMember(
|
const member = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.UnderReview
|
WorkspaceMemberStatus.UnderReview
|
||||||
);
|
);
|
||||||
t.is(member.status, WorkspaceMemberStatus.UnderReview);
|
t.is(member.status, WorkspaceMemberStatus.UnderReview);
|
||||||
@@ -919,7 +913,7 @@ test('should trigger workspace.members.requestDeclined event when delete workspa
|
|||||||
const member = await t.context.workspace.grantMember(
|
const member = await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
||||||
);
|
);
|
||||||
t.is(member.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
|
t.is(member.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
|
||||||
@@ -970,19 +964,19 @@ test('should refresh member seat status', async t => {
|
|||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser1.id,
|
otherUser1.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
||||||
);
|
);
|
||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser2.id,
|
otherUser2.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Pending
|
WorkspaceMemberStatus.Pending
|
||||||
);
|
);
|
||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser3.id,
|
otherUser3.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.NeedMoreSeat
|
WorkspaceMemberStatus.NeedMoreSeat
|
||||||
);
|
);
|
||||||
let count = await t.context.db.workspaceUserPermission.count({
|
let count = await t.context.db.workspaceUserPermission.count({
|
||||||
@@ -1043,30 +1037,30 @@ test('should find the workspace members order by type:desc and createdAt:asc', a
|
|||||||
await t.context.workspace.grantMember(
|
await t.context.workspace.grantMember(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
otherUser.id,
|
otherUser.id,
|
||||||
Permission.Read,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let members = await t.context.workspace.findMembers(workspace.id);
|
let members = await t.context.workspace.findMembers(workspace.id);
|
||||||
t.is(members.length, 8);
|
t.is(members.length, 8);
|
||||||
t.is(members[0].type, Permission.Owner);
|
t.is(members[0].type, WorkspaceRole.Owner);
|
||||||
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
|
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
|
||||||
for (let i = 1; i < 8; i++) {
|
for (let i = 1; i < 8; i++) {
|
||||||
t.is(members[i].type, Permission.Read);
|
t.is(members[i].type, WorkspaceRole.Collaborator);
|
||||||
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
|
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
|
||||||
}
|
}
|
||||||
members = await t.context.workspace.findMembers(workspace.id, { take: 100 });
|
members = await t.context.workspace.findMembers(workspace.id, { take: 100 });
|
||||||
t.is(members.length, 11);
|
t.is(members.length, 11);
|
||||||
t.is(members[0].type, Permission.Owner);
|
t.is(members[0].type, WorkspaceRole.Owner);
|
||||||
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
|
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
|
||||||
for (let i = 1; i < 11; i++) {
|
for (let i = 1; i < 11; i++) {
|
||||||
t.is(members[i].type, Permission.Read);
|
t.is(members[i].type, WorkspaceRole.Collaborator);
|
||||||
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
|
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
|
||||||
}
|
}
|
||||||
// skip should work
|
// skip should work
|
||||||
members = await t.context.workspace.findMembers(workspace.id, { skip: 5 });
|
members = await t.context.workspace.findMembers(workspace.id, { skip: 5 });
|
||||||
t.is(members.length, 6);
|
t.is(members.length, 6);
|
||||||
t.is(members[0].type, Permission.Read);
|
t.is(members[0].type, WorkspaceRole.Collaborator);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should get the workspace member invitation', async t => {
|
test('should get the workspace member invitation', async t => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { AppModule } from '../app.module';
|
|||||||
import { EventBus } from '../base';
|
import { EventBus } from '../base';
|
||||||
import { AuthService } from '../core/auth';
|
import { AuthService } from '../core/auth';
|
||||||
import { DocContentService } from '../core/doc-renderer';
|
import { DocContentService } from '../core/doc-renderer';
|
||||||
import { Permission, PermissionService } from '../core/permission';
|
import { PermissionService, WorkspaceRole } from '../core/permission';
|
||||||
import { QuotaManagementService, QuotaService, QuotaType } from '../core/quota';
|
import { QuotaManagementService, QuotaService, QuotaType } from '../core/quota';
|
||||||
import { WorkspaceType } from '../core/workspaces';
|
import { WorkspaceType } from '../core/workspaces';
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
inviteUser,
|
inviteUser,
|
||||||
inviteUsers,
|
inviteUsers,
|
||||||
leaveWorkspace,
|
leaveWorkspace,
|
||||||
PermissionEnum,
|
|
||||||
revokeInviteLink,
|
revokeInviteLink,
|
||||||
revokeMember,
|
revokeMember,
|
||||||
revokeUser,
|
revokeUser,
|
||||||
@@ -105,7 +104,7 @@ const init = async (
|
|||||||
|
|
||||||
const invite = async (
|
const invite = async (
|
||||||
email: string,
|
email: string,
|
||||||
permission: PermissionEnum = 'Write',
|
permission: WorkspaceRole = WorkspaceRole.Collaborator,
|
||||||
shouldSendEmail: boolean = false
|
shouldSendEmail: boolean = false
|
||||||
) => {
|
) => {
|
||||||
const member = await signUp(app, email.split('@')[0], email, '123456');
|
const member = await signUp(app, email.split('@')[0], email, '123456');
|
||||||
@@ -193,9 +192,12 @@ const init = async (
|
|||||||
] as const;
|
] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
const admin = await invite(`${prefix}admin@affine.pro`, 'Admin');
|
const admin = await invite(`${prefix}admin@affine.pro`, WorkspaceRole.Admin);
|
||||||
const write = await invite(`${prefix}write@affine.pro`);
|
const write = await invite(`${prefix}write@affine.pro`);
|
||||||
const read = await invite(`${prefix}read@affine.pro`, 'Read');
|
const read = await invite(
|
||||||
|
`${prefix}read@affine.pro`,
|
||||||
|
WorkspaceRole.Collaborator
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
invite,
|
invite,
|
||||||
@@ -268,7 +270,7 @@ test('should be able to check seat limit', async t => {
|
|||||||
{
|
{
|
||||||
// invite
|
// invite
|
||||||
await t.throwsAsync(
|
await t.throwsAsync(
|
||||||
invite('member3@affine.pro', 'Read'),
|
invite('member3@affine.pro', WorkspaceRole.Collaborator),
|
||||||
{ message: 'You have exceeded your workspace member quota.' },
|
{ message: 'You have exceeded your workspace member quota.' },
|
||||||
'should throw error if exceed member limit'
|
'should throw error if exceed member limit'
|
||||||
);
|
);
|
||||||
@@ -276,7 +278,7 @@ test('should be able to check seat limit', async t => {
|
|||||||
memberLimit: 5,
|
memberLimit: 5,
|
||||||
});
|
});
|
||||||
await t.notThrowsAsync(
|
await t.notThrowsAsync(
|
||||||
invite('member4@affine.pro', 'Read'),
|
invite('member4@affine.pro', WorkspaceRole.Collaborator),
|
||||||
'should not throw error if not exceed member limit'
|
'should not throw error if not exceed member limit'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -324,17 +326,35 @@ test('should be able to grant team member permission', async t => {
|
|||||||
const { owner, teamWorkspace: ws, admin, write, read } = await init(app);
|
const { owner, teamWorkspace: ws, admin, write, read } = await init(app);
|
||||||
|
|
||||||
await t.throwsAsync(
|
await t.throwsAsync(
|
||||||
grantMember(app, read.token.token, ws.id, write.id, 'Write'),
|
grantMember(
|
||||||
|
app,
|
||||||
|
read.token.token,
|
||||||
|
ws.id,
|
||||||
|
write.id,
|
||||||
|
WorkspaceRole.Collaborator
|
||||||
|
),
|
||||||
{ instanceOf: Error },
|
{ instanceOf: Error },
|
||||||
'should throw error if not owner'
|
'should throw error if not owner'
|
||||||
);
|
);
|
||||||
await t.throwsAsync(
|
await t.throwsAsync(
|
||||||
grantMember(app, write.token.token, ws.id, read.id, 'Write'),
|
grantMember(
|
||||||
|
app,
|
||||||
|
write.token.token,
|
||||||
|
ws.id,
|
||||||
|
read.id,
|
||||||
|
WorkspaceRole.Collaborator
|
||||||
|
),
|
||||||
{ instanceOf: Error },
|
{ instanceOf: Error },
|
||||||
'should throw error if not owner'
|
'should throw error if not owner'
|
||||||
);
|
);
|
||||||
await t.throwsAsync(
|
await t.throwsAsync(
|
||||||
grantMember(app, admin.token.token, ws.id, read.id, 'Write'),
|
grantMember(
|
||||||
|
app,
|
||||||
|
admin.token.token,
|
||||||
|
ws.id,
|
||||||
|
read.id,
|
||||||
|
WorkspaceRole.Collaborator
|
||||||
|
),
|
||||||
{ instanceOf: Error },
|
{ instanceOf: Error },
|
||||||
'should throw error if not owner'
|
'should throw error if not owner'
|
||||||
);
|
);
|
||||||
@@ -342,15 +362,29 @@ test('should be able to grant team member permission', async t => {
|
|||||||
{
|
{
|
||||||
// owner should be able to grant permission
|
// owner should be able to grant permission
|
||||||
t.true(
|
t.true(
|
||||||
await permissions.tryCheckWorkspaceIs(ws.id, read.id, Permission.Read),
|
await permissions.tryCheckWorkspaceIs(
|
||||||
|
ws.id,
|
||||||
|
read.id,
|
||||||
|
WorkspaceRole.Collaborator
|
||||||
|
),
|
||||||
'should be able to check permission'
|
'should be able to check permission'
|
||||||
);
|
);
|
||||||
t.truthy(
|
t.truthy(
|
||||||
await grantMember(app, owner.token.token, ws.id, read.id, 'Admin'),
|
await grantMember(
|
||||||
|
app,
|
||||||
|
owner.token.token,
|
||||||
|
ws.id,
|
||||||
|
read.id,
|
||||||
|
WorkspaceRole.Admin
|
||||||
|
),
|
||||||
'should be able to grant permission'
|
'should be able to grant permission'
|
||||||
);
|
);
|
||||||
t.true(
|
t.true(
|
||||||
await permissions.tryCheckWorkspaceIs(ws.id, read.id, Permission.Admin),
|
await permissions.tryCheckWorkspaceIs(
|
||||||
|
ws.id,
|
||||||
|
read.id,
|
||||||
|
WorkspaceRole.Admin
|
||||||
|
),
|
||||||
'should be able to check permission'
|
'should be able to check permission'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -692,17 +726,33 @@ test('should be able to emit events', async t => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const { teamWorkspace: tws, owner, read } = await init(app);
|
const { teamWorkspace: tws, owner, read } = await init(app);
|
||||||
await grantMember(app, owner.token.token, tws.id, read.id, 'Admin');
|
await grantMember(
|
||||||
|
app,
|
||||||
|
owner.token.token,
|
||||||
|
tws.id,
|
||||||
|
read.id,
|
||||||
|
WorkspaceRole.Admin
|
||||||
|
);
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
event.emit.lastCall.args,
|
event.emit.lastCall.args,
|
||||||
[
|
[
|
||||||
'workspace.members.roleChanged',
|
'workspace.members.roleChanged',
|
||||||
{ userId: read.id, workspaceId: tws.id, permission: Permission.Admin },
|
{
|
||||||
|
userId: read.id,
|
||||||
|
workspaceId: tws.id,
|
||||||
|
permission: WorkspaceRole.Admin,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'should emit role changed event'
|
'should emit role changed event'
|
||||||
);
|
);
|
||||||
|
|
||||||
await grantMember(app, owner.token.token, tws.id, read.id, 'Owner');
|
await grantMember(
|
||||||
|
app,
|
||||||
|
owner.token.token,
|
||||||
|
tws.id,
|
||||||
|
read.id,
|
||||||
|
WorkspaceRole.Owner
|
||||||
|
);
|
||||||
const [ownershipTransferred] = event.emit
|
const [ownershipTransferred] = event.emit
|
||||||
.getCalls()
|
.getCalls()
|
||||||
.map(call => call.args)
|
.map(call => call.args)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
|
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
|
||||||
|
|
||||||
import { Permission } from '../../core/permission';
|
import { WorkspaceRole } from '../../core/permission';
|
||||||
import { UserType } from '../../core/user/types';
|
import { UserType } from '../../core/user/types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -14,7 +14,7 @@ export class WorkspaceResolverMock {
|
|||||||
public: false,
|
public: false,
|
||||||
permissions: {
|
permissions: {
|
||||||
create: {
|
create: {
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
status: WorkspaceMemberStatus.Accepted,
|
status: WorkspaceMemberStatus.Accepted,
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { INestApplication, ModuleMetadata } from '@nestjs/common';
|
import {
|
||||||
|
ConsoleLogger,
|
||||||
|
INestApplication,
|
||||||
|
ModuleMetadata,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { Query, Resolver } from '@nestjs/graphql';
|
import { Query, Resolver } from '@nestjs/graphql';
|
||||||
import { Test, TestingModuleBuilder } from '@nestjs/testing';
|
import { Test, TestingModuleBuilder } from '@nestjs/testing';
|
||||||
@@ -15,8 +19,6 @@ import { AuthGuard, AuthModule } from '../../core/auth';
|
|||||||
import { UserFeaturesInit1698652531198 } from '../../data/migrations/1698652531198-user-features-init';
|
import { UserFeaturesInit1698652531198 } from '../../data/migrations/1698652531198-user-features-init';
|
||||||
import { ModelsModule } from '../../models';
|
import { ModelsModule } from '../../models';
|
||||||
|
|
||||||
export type PermissionEnum = 'Owner' | 'Admin' | 'Write' | 'Read';
|
|
||||||
|
|
||||||
async function flushDB(client: PrismaClient) {
|
async function flushDB(client: PrismaClient) {
|
||||||
const result: { tablename: string }[] =
|
const result: { tablename: string }[] =
|
||||||
await client.$queryRaw`SELECT tablename
|
await client.$queryRaw`SELECT tablename
|
||||||
@@ -133,8 +135,11 @@ export async function createTestingApp(moduleDef: TestingModuleMeatdata = {}) {
|
|||||||
cors: true,
|
cors: true,
|
||||||
bodyParser: true,
|
bodyParser: true,
|
||||||
rawBody: true,
|
rawBody: true,
|
||||||
logger: ['fatal'],
|
|
||||||
});
|
});
|
||||||
|
const logger = new ConsoleLogger();
|
||||||
|
|
||||||
|
logger.setLogLevels(['fatal']);
|
||||||
|
app.useLogger(logger);
|
||||||
|
|
||||||
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
|
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
|
||||||
app.use(
|
app.use(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { INestApplication } from '@nestjs/common';
|
import type { INestApplication } from '@nestjs/common';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
|
import { WorkspaceRole } from '../../core/permission/types';
|
||||||
import type { WorkspaceType } from '../../core/workspaces';
|
import type { WorkspaceType } from '../../core/workspaces';
|
||||||
import { gql } from './common';
|
import { gql } from './common';
|
||||||
import { PermissionEnum } from './utils';
|
|
||||||
|
|
||||||
export async function createWorkspace(
|
export async function createWorkspace(
|
||||||
app: INestApplication,
|
app: INestApplication,
|
||||||
@@ -157,7 +157,7 @@ export async function grantMember(
|
|||||||
token: string,
|
token: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
permission: PermissionEnum
|
permission: WorkspaceRole
|
||||||
) {
|
) {
|
||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.post(gql)
|
.post(gql)
|
||||||
@@ -169,7 +169,7 @@ export async function grantMember(
|
|||||||
grantMember(
|
grantMember(
|
||||||
workspaceId: "${workspaceId}"
|
workspaceId: "${workspaceId}"
|
||||||
userId: "${userId}"
|
userId: "${userId}"
|
||||||
permission: ${permission}
|
permission: ${WorkspaceRole[permission]}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import ava from 'ava';
|
|||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
import { AppModule } from '../app.module';
|
import { AppModule } from '../app.module';
|
||||||
|
import { WorkspaceRole } from '../core/permission/types';
|
||||||
import {
|
import {
|
||||||
acceptInviteById,
|
acceptInviteById,
|
||||||
createTestingApp,
|
createTestingApp,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
getWorkspacePublicPages,
|
getWorkspacePublicPages,
|
||||||
|
grantMember,
|
||||||
inviteUser,
|
inviteUser,
|
||||||
publishPage,
|
publishPage,
|
||||||
revokePublicPage,
|
revokePublicPage,
|
||||||
@@ -116,7 +118,7 @@ test('should share a page', async t => {
|
|||||||
const msg1 = await publishPage(app, u2.token.token, 'not_exists_ws', 'page2');
|
const msg1 = await publishPage(app, u2.token.token, 'not_exists_ws', 'page2');
|
||||||
t.is(
|
t.is(
|
||||||
msg1,
|
msg1,
|
||||||
'You do not have permission to access Space not_exists_ws.',
|
'You do not have permission to access doc page2 under Space not_exists_ws.',
|
||||||
'unauthorized user can share page'
|
'unauthorized user can share page'
|
||||||
);
|
);
|
||||||
const msg2 = await revokePublicPage(
|
const msg2 = await revokePublicPage(
|
||||||
@@ -127,7 +129,7 @@ test('should share a page', async t => {
|
|||||||
);
|
);
|
||||||
t.is(
|
t.is(
|
||||||
msg2,
|
msg2,
|
||||||
'You do not have permission to access Space not_exists_ws.',
|
'You do not have permission to access doc page2 under Space not_exists_ws.',
|
||||||
'unauthorized user can share page'
|
'unauthorized user can share page'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -136,6 +138,21 @@ test('should share a page', async t => {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
await inviteUser(app, u1.token.token, workspace.id, u2.email)
|
await inviteUser(app, u1.token.token, workspace.id, u2.email)
|
||||||
);
|
);
|
||||||
|
const msg3 = await publishPage(app, u2.token.token, workspace.id, 'page2');
|
||||||
|
t.is(
|
||||||
|
msg3,
|
||||||
|
`You do not have permission to access doc page2 under Space ${workspace.id}.`,
|
||||||
|
'WorkspaceRole and PageRole is lower than required'
|
||||||
|
);
|
||||||
|
|
||||||
|
await grantMember(
|
||||||
|
app,
|
||||||
|
u1.token.token,
|
||||||
|
workspace.id,
|
||||||
|
u2.id,
|
||||||
|
WorkspaceRole.Admin
|
||||||
|
);
|
||||||
|
|
||||||
const invited = await publishPage(app, u2.token.token, workspace.id, 'page2');
|
const invited = await publishPage(app, u2.token.token, workspace.id, 'page2');
|
||||||
t.is(invited.id, 'page2', 'failed to share page');
|
t.is(invited.id, 'page2', 'failed to share page');
|
||||||
|
|
||||||
@@ -154,21 +171,21 @@ test('should share a page', async t => {
|
|||||||
t.is(pages2.length, 1, 'failed to get shared pages');
|
t.is(pages2.length, 1, 'failed to get shared pages');
|
||||||
t.is(pages2[0].id, 'page2', 'failed to get shared page: page2');
|
t.is(pages2[0].id, 'page2', 'failed to get shared page: page2');
|
||||||
|
|
||||||
const msg3 = await revokePublicPage(
|
const msg4 = await revokePublicPage(
|
||||||
app,
|
app,
|
||||||
u1.token.token,
|
u1.token.token,
|
||||||
workspace.id,
|
workspace.id,
|
||||||
'page3'
|
'page3'
|
||||||
);
|
);
|
||||||
t.is(msg3, 'Page is not public');
|
t.is(msg4, 'Page is not public');
|
||||||
|
|
||||||
const msg4 = await revokePublicPage(
|
const revoked = await revokePublicPage(
|
||||||
app,
|
app,
|
||||||
u1.token.token,
|
u1.token.token,
|
||||||
workspace.id,
|
workspace.id,
|
||||||
'page2'
|
'page2'
|
||||||
);
|
);
|
||||||
t.false(msg4.public, 'failed to revoke page');
|
t.false(revoked.public, 'failed to revoke page');
|
||||||
const page3 = await getWorkspacePublicPages(
|
const page3 = await getWorkspacePublicPages(
|
||||||
app,
|
app,
|
||||||
u1.token.token,
|
u1.token.token,
|
||||||
@@ -177,7 +194,7 @@ test('should share a page', async t => {
|
|||||||
t.is(page3.length, 0, 'failed to get shared pages');
|
t.is(page3.length, 0, 'failed to get shared pages');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should can get workspace doc', async t => {
|
test('should be able to get workspace doc', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '2');
|
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '2');
|
||||||
|
|||||||
@@ -363,6 +363,11 @@ export const USER_FRIENDLY_ERRORS = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Workspace & Userspace & Doc & Sync errors
|
// Workspace & Userspace & Doc & Sync errors
|
||||||
|
workspace_permission_not_found: {
|
||||||
|
type: 'internal_server_error',
|
||||||
|
args: { spaceId: 'string' },
|
||||||
|
message: ({ spaceId }) => `Space ${spaceId} permission not found.`,
|
||||||
|
},
|
||||||
space_not_found: {
|
space_not_found: {
|
||||||
type: 'resource_not_found',
|
type: 'resource_not_found',
|
||||||
args: { spaceId: 'string' },
|
args: { spaceId: 'string' },
|
||||||
@@ -395,6 +400,11 @@ export const USER_FRIENDLY_ERRORS = {
|
|||||||
args: { spaceId: 'string' },
|
args: { spaceId: 'string' },
|
||||||
message: ({ spaceId }) => `Owner of Space ${spaceId} not found.`,
|
message: ({ spaceId }) => `Owner of Space ${spaceId} not found.`,
|
||||||
},
|
},
|
||||||
|
space_should_have_only_one_owner: {
|
||||||
|
type: 'invalid_input',
|
||||||
|
args: { spaceId: 'string' },
|
||||||
|
message: 'Space should have only one owner.',
|
||||||
|
},
|
||||||
doc_not_found: {
|
doc_not_found: {
|
||||||
type: 'resource_not_found',
|
type: 'resource_not_found',
|
||||||
args: { spaceId: 'string', docId: 'string' },
|
args: { spaceId: 'string', docId: 'string' },
|
||||||
@@ -438,6 +448,24 @@ export const USER_FRIENDLY_ERRORS = {
|
|||||||
type: 'invalid_input',
|
type: 'invalid_input',
|
||||||
message: 'Expected to revoke a public page, not a Space.',
|
message: 'Expected to revoke a public page, not a Space.',
|
||||||
},
|
},
|
||||||
|
expect_to_grant_doc_user_roles: {
|
||||||
|
type: 'invalid_input',
|
||||||
|
args: { spaceId: 'string', docId: 'string' },
|
||||||
|
message: ({ spaceId, docId }) =>
|
||||||
|
`Expect grant roles on doc ${docId} under Space ${spaceId}, not a Space.`,
|
||||||
|
},
|
||||||
|
expect_to_revoke_doc_user_roles: {
|
||||||
|
type: 'invalid_input',
|
||||||
|
args: { spaceId: 'string', docId: 'string' },
|
||||||
|
message: ({ spaceId, docId }) =>
|
||||||
|
`Expect revoke roles on doc ${docId} under Space ${spaceId}, not a Space.`,
|
||||||
|
},
|
||||||
|
expect_to_update_doc_user_role: {
|
||||||
|
type: 'invalid_input',
|
||||||
|
args: { spaceId: 'string', docId: 'string' },
|
||||||
|
message: ({ spaceId, docId }) =>
|
||||||
|
`Expect update roles on doc ${docId} under Space ${spaceId}, not a Space.`,
|
||||||
|
},
|
||||||
page_is_not_public: {
|
page_is_not_public: {
|
||||||
type: 'bad_request',
|
type: 'bad_request',
|
||||||
message: 'Page is not public.',
|
message: 'Page is not public.',
|
||||||
|
|||||||
@@ -191,6 +191,16 @@ export class EmailVerificationRequired extends UserFriendlyError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
|
class WorkspacePermissionNotFoundDataType {
|
||||||
|
@Field() spaceId!: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkspacePermissionNotFound extends UserFriendlyError {
|
||||||
|
constructor(args: WorkspacePermissionNotFoundDataType, message?: string | ((args: WorkspacePermissionNotFoundDataType) => string)) {
|
||||||
|
super('internal_server_error', 'workspace_permission_not_found', message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ObjectType()
|
||||||
class SpaceNotFoundDataType {
|
class SpaceNotFoundDataType {
|
||||||
@Field() spaceId!: string
|
@Field() spaceId!: string
|
||||||
}
|
}
|
||||||
@@ -251,6 +261,16 @@ export class SpaceOwnerNotFound extends UserFriendlyError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
|
class SpaceShouldHaveOnlyOneOwnerDataType {
|
||||||
|
@Field() spaceId!: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpaceShouldHaveOnlyOneOwner extends UserFriendlyError {
|
||||||
|
constructor(args: SpaceShouldHaveOnlyOneOwnerDataType, message?: string | ((args: SpaceShouldHaveOnlyOneOwnerDataType) => string)) {
|
||||||
|
super('invalid_input', 'space_should_have_only_one_owner', message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ObjectType()
|
||||||
class DocNotFoundDataType {
|
class DocNotFoundDataType {
|
||||||
@Field() spaceId!: string
|
@Field() spaceId!: string
|
||||||
@Field() docId!: string
|
@Field() docId!: string
|
||||||
@@ -328,6 +348,39 @@ export class ExpectToRevokePublicPage extends UserFriendlyError {
|
|||||||
super('invalid_input', 'expect_to_revoke_public_page', message);
|
super('invalid_input', 'expect_to_revoke_public_page', message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ObjectType()
|
||||||
|
class ExpectToGrantDocUserRolesDataType {
|
||||||
|
@Field() spaceId!: string
|
||||||
|
@Field() docId!: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExpectToGrantDocUserRoles extends UserFriendlyError {
|
||||||
|
constructor(args: ExpectToGrantDocUserRolesDataType, message?: string | ((args: ExpectToGrantDocUserRolesDataType) => string)) {
|
||||||
|
super('invalid_input', 'expect_to_grant_doc_user_roles', message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ObjectType()
|
||||||
|
class ExpectToRevokeDocUserRolesDataType {
|
||||||
|
@Field() spaceId!: string
|
||||||
|
@Field() docId!: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExpectToRevokeDocUserRoles extends UserFriendlyError {
|
||||||
|
constructor(args: ExpectToRevokeDocUserRolesDataType, message?: string | ((args: ExpectToRevokeDocUserRolesDataType) => string)) {
|
||||||
|
super('invalid_input', 'expect_to_revoke_doc_user_roles', message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ObjectType()
|
||||||
|
class ExpectToUpdateDocUserRoleDataType {
|
||||||
|
@Field() spaceId!: string
|
||||||
|
@Field() docId!: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExpectToUpdateDocUserRole extends UserFriendlyError {
|
||||||
|
constructor(args: ExpectToUpdateDocUserRoleDataType, message?: string | ((args: ExpectToUpdateDocUserRoleDataType) => string)) {
|
||||||
|
super('invalid_input', 'expect_to_update_doc_user_role', message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PageIsNotPublic extends UserFriendlyError {
|
export class PageIsNotPublic extends UserFriendlyError {
|
||||||
constructor(message?: string) {
|
constructor(message?: string) {
|
||||||
@@ -679,12 +732,14 @@ export enum ErrorNames {
|
|||||||
ACTION_FORBIDDEN,
|
ACTION_FORBIDDEN,
|
||||||
ACCESS_DENIED,
|
ACCESS_DENIED,
|
||||||
EMAIL_VERIFICATION_REQUIRED,
|
EMAIL_VERIFICATION_REQUIRED,
|
||||||
|
WORKSPACE_PERMISSION_NOT_FOUND,
|
||||||
SPACE_NOT_FOUND,
|
SPACE_NOT_FOUND,
|
||||||
MEMBER_NOT_FOUND_IN_SPACE,
|
MEMBER_NOT_FOUND_IN_SPACE,
|
||||||
NOT_IN_SPACE,
|
NOT_IN_SPACE,
|
||||||
ALREADY_IN_SPACE,
|
ALREADY_IN_SPACE,
|
||||||
SPACE_ACCESS_DENIED,
|
SPACE_ACCESS_DENIED,
|
||||||
SPACE_OWNER_NOT_FOUND,
|
SPACE_OWNER_NOT_FOUND,
|
||||||
|
SPACE_SHOULD_HAVE_ONLY_ONE_OWNER,
|
||||||
DOC_NOT_FOUND,
|
DOC_NOT_FOUND,
|
||||||
DOC_ACCESS_DENIED,
|
DOC_ACCESS_DENIED,
|
||||||
VERSION_REJECTED,
|
VERSION_REJECTED,
|
||||||
@@ -693,6 +748,9 @@ export enum ErrorNames {
|
|||||||
BLOB_NOT_FOUND,
|
BLOB_NOT_FOUND,
|
||||||
EXPECT_TO_PUBLISH_PAGE,
|
EXPECT_TO_PUBLISH_PAGE,
|
||||||
EXPECT_TO_REVOKE_PUBLIC_PAGE,
|
EXPECT_TO_REVOKE_PUBLIC_PAGE,
|
||||||
|
EXPECT_TO_GRANT_DOC_USER_ROLES,
|
||||||
|
EXPECT_TO_REVOKE_DOC_USER_ROLES,
|
||||||
|
EXPECT_TO_UPDATE_DOC_USER_ROLE,
|
||||||
PAGE_IS_NOT_PUBLIC,
|
PAGE_IS_NOT_PUBLIC,
|
||||||
FAILED_TO_SAVE_UPDATES,
|
FAILED_TO_SAVE_UPDATES,
|
||||||
FAILED_TO_UPSERT_SNAPSHOT,
|
FAILED_TO_UPSERT_SNAPSHOT,
|
||||||
@@ -746,5 +804,5 @@ registerEnumType(ErrorNames, {
|
|||||||
export const ErrorDataUnionType = createUnionType({
|
export const ErrorDataUnionType = createUnionType({
|
||||||
name: 'ErrorDataUnion',
|
name: 'ErrorDataUnion',
|
||||||
types: () =>
|
types: () =>
|
||||||
[QueryTooLongDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType] as const,
|
[QueryTooLongDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType] as const,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
import { CannotDeleteAllAdminAccount } from '../../base';
|
import { CannotDeleteAllAdminAccount } from '../../base';
|
||||||
import { WorkspaceType } from '../workspaces/types';
|
import { WorkspaceFeatureType } from '../workspaces/types';
|
||||||
import { FeatureConfigType, getFeature } from './feature';
|
import { FeatureConfigType, getFeature } from './feature';
|
||||||
import { FeatureKind, FeatureType } from './types';
|
import { FeatureKind, FeatureType } from './types';
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export class FeatureService {
|
|||||||
if (data) {
|
if (data) {
|
||||||
return getFeature(this.prisma, data.id) as Promise<FeatureConfigType<F>>;
|
return getFeature(this.prisma, data.id) as Promise<FeatureConfigType<F>>;
|
||||||
}
|
}
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== User Features ========
|
// ======== User Features ========
|
||||||
@@ -315,7 +315,7 @@ export class FeatureService {
|
|||||||
|
|
||||||
async listWorkspacesByFeature(
|
async listWorkspacesByFeature(
|
||||||
feature: FeatureType
|
feature: FeatureType
|
||||||
): Promise<WorkspaceType[]> {
|
): Promise<WorkspaceFeatureType[]> {
|
||||||
return this.prisma.workspaceFeature
|
return this.prisma.workspaceFeature
|
||||||
.findMany({
|
.findMany({
|
||||||
where: {
|
where: {
|
||||||
@@ -335,7 +335,7 @@ export class FeatureService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(wss => wss.map(ws => ws.workspace as WorkspaceType));
|
.then(wss => wss.map(ws => ws.workspace));
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasWorkspaceFeature(workspaceId: string, feature: FeatureType) {
|
async hasWorkspaceFeature(workspaceId: string, feature: FeatureType) {
|
||||||
|
|||||||
@@ -0,0 +1,665 @@
|
|||||||
|
# Snapshot report for `src/core/permission/__tests__/role.spec.ts`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `role.spec.ts.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://avajs.dev).
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: External and DocRole: External
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: false,
|
||||||
|
Doc_Duplicate: false,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: false,
|
||||||
|
Doc_Publish: false,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: false,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: false,
|
||||||
|
Doc_Update: false,
|
||||||
|
Doc_Users_Manage: false,
|
||||||
|
Doc_Users_Read: false,
|
||||||
|
Workspace_CreateDoc: false,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: false,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: false,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: false,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Reader
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: false,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: false,
|
||||||
|
Doc_Publish: false,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: false,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: false,
|
||||||
|
Doc_Update: false,
|
||||||
|
Doc_Users_Manage: false,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: false,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: false,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: false,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: false,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Editor
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: false,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: false,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: false,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: false,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: false,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: false,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Manager
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: false,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: false,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: false,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: false,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Owner
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: true,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: false,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: false,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: false,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: false,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: External
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: false,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: false,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Reader
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: false,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: false,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Editor
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: false,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: false,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Manager
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Owner
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: true,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: false,
|
||||||
|
Workspace_Properties_Delete: false,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: false,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: false,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: false,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: External
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Reader
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Editor
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Manager
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Owner
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: true,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: false,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: false,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: External
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: true,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: true,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Reader
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: true,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: true,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Editor
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: true,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: true,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Manager
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: false,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: true,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: true,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Owner
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
Doc_Copy: true,
|
||||||
|
Doc_Delete: true,
|
||||||
|
Doc_Duplicate: true,
|
||||||
|
Doc_Properties_Read: true,
|
||||||
|
Doc_Properties_Update: true,
|
||||||
|
Doc_Publish: true,
|
||||||
|
Doc_Read: true,
|
||||||
|
Doc_Restore: true,
|
||||||
|
Doc_TransferOwner: true,
|
||||||
|
Doc_Trash: true,
|
||||||
|
Doc_Update: true,
|
||||||
|
Doc_Users_Manage: true,
|
||||||
|
Doc_Users_Read: true,
|
||||||
|
Workspace_CreateDoc: true,
|
||||||
|
Workspace_Delete: true,
|
||||||
|
Workspace_Organize_Read: true,
|
||||||
|
Workspace_Properties_Create: true,
|
||||||
|
Workspace_Properties_Delete: true,
|
||||||
|
Workspace_Properties_Read: true,
|
||||||
|
Workspace_Properties_Update: true,
|
||||||
|
Workspace_Settings_Read: true,
|
||||||
|
Workspace_Settings_Update: true,
|
||||||
|
Workspace_Sync: true,
|
||||||
|
Workspace_TransferOwner: true,
|
||||||
|
Workspace_Users_Manage: true,
|
||||||
|
Workspace_Users_Read: true,
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,36 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
|
||||||
|
import { DocRole, WorkspaceRole } from '../index';
|
||||||
|
import { Actions, ActionsKeys, mapRoleToActions } from '../types';
|
||||||
|
|
||||||
|
// create a matrix representing the all possible permission of WorkspaceRole and DocRole
|
||||||
|
const matrix = Object.values(WorkspaceRole)
|
||||||
|
.filter(r => typeof r !== 'string')
|
||||||
|
.flatMap(workspaceRole =>
|
||||||
|
Object.values(DocRole)
|
||||||
|
.filter(r => typeof r !== 'string')
|
||||||
|
.map(docRole => ({
|
||||||
|
workspaceRole,
|
||||||
|
docRole,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const { workspaceRole, docRole } of matrix) {
|
||||||
|
const permission = mapRoleToActions(workspaceRole, docRole);
|
||||||
|
test(`should be able to get correct permissions from WorkspaceRole: ${WorkspaceRole[workspaceRole]} and DocRole: ${DocRole[docRole]}`, t => {
|
||||||
|
t.snapshot(permission);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('ActionsKeys value should be the same order of the Actions objects', t => {
|
||||||
|
for (const [index, value] of ActionsKeys.entries()) {
|
||||||
|
const [k, k1, k2] = value.split('.');
|
||||||
|
if (k2) {
|
||||||
|
// @ts-expect-error
|
||||||
|
t.is(Actions[k][k1][k2], index);
|
||||||
|
} else {
|
||||||
|
// @ts-expect-error
|
||||||
|
t.is(Actions[k][k1], index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -9,4 +9,4 @@ import { PermissionService } from './service';
|
|||||||
export class PermissionModule {}
|
export class PermissionModule {}
|
||||||
|
|
||||||
export { PermissionService } from './service';
|
export { PermissionService } from './service';
|
||||||
export { Permission, PublicPageMode } from './types';
|
export { DocRole, PublicPageMode, WorkspaceRole } from './types';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import type { Prisma } from '@prisma/client';
|
import type { Prisma } from '@prisma/client';
|
||||||
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
|
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
|
||||||
import { groupBy } from 'lodash-es';
|
import { groupBy } from 'lodash-es';
|
||||||
@@ -8,11 +8,22 @@ import {
|
|||||||
EventBus,
|
EventBus,
|
||||||
SpaceAccessDenied,
|
SpaceAccessDenied,
|
||||||
SpaceOwnerNotFound,
|
SpaceOwnerNotFound,
|
||||||
|
SpaceShouldHaveOnlyOneOwner,
|
||||||
|
WorkspacePermissionNotFound,
|
||||||
} from '../../base';
|
} from '../../base';
|
||||||
import { Permission, PublicPageMode } from './types';
|
import {
|
||||||
|
AllPossibleGraphQLDocActionsKeys,
|
||||||
|
DocRole,
|
||||||
|
findMinimalDocRole,
|
||||||
|
PublicPageMode,
|
||||||
|
requiredWorkspaceRoleByDocRole,
|
||||||
|
WorkspaceRole,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PermissionService {
|
export class PermissionService {
|
||||||
|
private readonly logger = new Logger(PermissionService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaClient,
|
private readonly prisma: PrismaClient,
|
||||||
private readonly event: EventBus
|
private readonly event: EventBus
|
||||||
@@ -30,7 +41,7 @@ export class PermissionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start regin: workspace permission
|
/// Start regin: workspace permission
|
||||||
async get(ws: string, user: string) {
|
async get(ws: string, user: string): Promise<WorkspaceRole> {
|
||||||
const data = await this.prisma.workspaceUserPermission.findFirst({
|
const data = await this.prisma.workspaceUserPermission.findFirst({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: ws,
|
workspaceId: ws,
|
||||||
@@ -39,7 +50,11 @@ export class PermissionService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return data?.type as Permission;
|
if (!data) {
|
||||||
|
throw new WorkspacePermissionNotFound({ spaceId: ws });
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +78,7 @@ export class PermissionService {
|
|||||||
.findMany({
|
.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
OR: this.acceptedCondition,
|
OR: this.acceptedCondition,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
@@ -77,7 +92,7 @@ export class PermissionService {
|
|||||||
const owner = await this.prisma.workspaceUserPermission.findFirst({
|
const owner = await this.prisma.workspaceUserPermission.findFirst({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: true,
|
||||||
@@ -95,7 +110,7 @@ export class PermissionService {
|
|||||||
const admin = await this.prisma.workspaceUserPermission.findMany({
|
const admin = await this.prisma.workspaceUserPermission.findMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
type: Permission.Admin,
|
type: WorkspaceRole.Admin,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: true,
|
||||||
@@ -117,7 +132,7 @@ export class PermissionService {
|
|||||||
return this.prisma.workspaceUserPermission.findFirst({
|
return this.prisma.workspaceUserPermission.findFirst({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: true,
|
||||||
@@ -136,7 +151,7 @@ export class PermissionService {
|
|||||||
if (ws === id) {
|
if (ws === id) {
|
||||||
// if workspace is public or have any public page, then allow to access
|
// if workspace is public or have any public page, then allow to access
|
||||||
const [isPublicWorkspace, publicPages] = await Promise.all([
|
const [isPublicWorkspace, publicPages] = await Promise.all([
|
||||||
this.tryCheckWorkspace(ws, user, Permission.Read),
|
this.tryCheckWorkspace(ws, user, WorkspaceRole.Collaborator),
|
||||||
this.prisma.workspacePage.count({
|
this.prisma.workspacePage.count({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: ws,
|
workspaceId: ws,
|
||||||
@@ -147,7 +162,7 @@ export class PermissionService {
|
|||||||
return isPublicWorkspace || publicPages > 0;
|
return isPublicWorkspace || publicPages > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.tryCheckPage(ws, id, user);
|
return this.tryCheckPage(ws, id, 'Doc_Read', user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaceMemberStatus(ws: string, user: string) {
|
async getWorkspaceMemberStatus(ws: string, user: string) {
|
||||||
@@ -168,7 +183,7 @@ export class PermissionService {
|
|||||||
async isWorkspaceMember(
|
async isWorkspaceMember(
|
||||||
ws: string,
|
ws: string,
|
||||||
user: string,
|
user: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const count = await this.prisma.workspaceUserPermission.count({
|
const count = await this.prisma.workspaceUserPermission.count({
|
||||||
where: {
|
where: {
|
||||||
@@ -193,7 +208,7 @@ export class PermissionService {
|
|||||||
async checkCloudWorkspace(
|
async checkCloudWorkspace(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
userId?: string,
|
userId?: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
const hasWorkspace = await this.hasWorkspace(workspaceId);
|
const hasWorkspace = await this.hasWorkspace(workspaceId);
|
||||||
if (hasWorkspace) {
|
if (hasWorkspace) {
|
||||||
@@ -204,7 +219,7 @@ export class PermissionService {
|
|||||||
async checkWorkspace(
|
async checkWorkspace(
|
||||||
ws: string,
|
ws: string,
|
||||||
user?: string,
|
user?: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
if (!(await this.tryCheckWorkspace(ws, user, permission))) {
|
if (!(await this.tryCheckWorkspace(ws, user, permission))) {
|
||||||
throw new SpaceAccessDenied({ spaceId: ws });
|
throw new SpaceAccessDenied({ spaceId: ws });
|
||||||
@@ -214,10 +229,10 @@ export class PermissionService {
|
|||||||
async tryCheckWorkspace(
|
async tryCheckWorkspace(
|
||||||
ws: string,
|
ws: string,
|
||||||
user?: string,
|
user?: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
// If the permission is read, we should check if the workspace is public
|
// If the permission is read, we should check if the workspace is public
|
||||||
if (permission === Permission.Read) {
|
if (permission === WorkspaceRole.Collaborator) {
|
||||||
const count = await this.prisma.workspace.count({
|
const count = await this.prisma.workspace.count({
|
||||||
where: { id: ws, public: true },
|
where: { id: ws, public: true },
|
||||||
});
|
});
|
||||||
@@ -242,7 +257,15 @@ export class PermissionService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return count > 0;
|
if (count > 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.logger.log("User's WorkspaceRole is lower than required", {
|
||||||
|
workspaceId: ws,
|
||||||
|
userId: user,
|
||||||
|
requiredRole: WorkspaceRole[permission],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsigned in, workspace is not public
|
// unsigned in, workspace is not public
|
||||||
@@ -253,7 +276,7 @@ export class PermissionService {
|
|||||||
async checkWorkspaceIs(
|
async checkWorkspaceIs(
|
||||||
ws: string,
|
ws: string,
|
||||||
user: string,
|
user: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
if (!(await this.tryCheckWorkspaceIs(ws, user, permission))) {
|
if (!(await this.tryCheckWorkspaceIs(ws, user, permission))) {
|
||||||
throw new SpaceAccessDenied({ spaceId: ws });
|
throw new SpaceAccessDenied({ spaceId: ws });
|
||||||
@@ -263,7 +286,7 @@ export class PermissionService {
|
|||||||
async tryCheckWorkspaceIs(
|
async tryCheckWorkspaceIs(
|
||||||
ws: string,
|
ws: string,
|
||||||
user: string,
|
user: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
const count = await this.prisma.workspaceUserPermission.count({
|
const count = await this.prisma.workspaceUserPermission.count({
|
||||||
where: {
|
where: {
|
||||||
@@ -307,7 +330,7 @@ export class PermissionService {
|
|||||||
async grant(
|
async grant(
|
||||||
ws: string,
|
ws: string,
|
||||||
user: string,
|
user: string,
|
||||||
permission: Permission = Permission.Read,
|
permission: WorkspaceRole = WorkspaceRole.Collaborator,
|
||||||
status: WorkspaceMemberStatus = WorkspaceMemberStatus.Pending
|
status: WorkspaceMemberStatus = WorkspaceMemberStatus.Pending
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const data = await this.prisma.workspaceUserPermission.findFirst({
|
const data = await this.prisma.workspaceUserPermission.findFirst({
|
||||||
@@ -315,7 +338,7 @@ export class PermissionService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const toBeOwner = permission === Permission.Owner;
|
const toBeOwner = permission === WorkspaceRole.Owner;
|
||||||
if (data.accepted && data.status === WorkspaceMemberStatus.Accepted) {
|
if (data.accepted && data.status === WorkspaceMemberStatus.Accepted) {
|
||||||
const [p] = await this.prisma.$transaction(
|
const [p] = await this.prisma.$transaction(
|
||||||
[
|
[
|
||||||
@@ -331,10 +354,10 @@ export class PermissionService {
|
|||||||
? this.prisma.workspaceUserPermission.updateMany({
|
? this.prisma.workspaceUserPermission.updateMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: ws,
|
workspaceId: ws,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
userId: { not: user },
|
userId: { not: user },
|
||||||
},
|
},
|
||||||
data: { type: Permission.Admin },
|
data: { type: WorkspaceRole.Admin },
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
].filter(Boolean) as Prisma.PrismaPromise<any>[]
|
].filter(Boolean) as Prisma.PrismaPromise<any>[]
|
||||||
@@ -441,7 +464,7 @@ export class PermissionService {
|
|||||||
|
|
||||||
// We shouldn't revoke owner permission
|
// We shouldn't revoke owner permission
|
||||||
// should auto deleted by workspace/user delete cascading
|
// should auto deleted by workspace/user delete cascading
|
||||||
if (!permission || permission.type === Permission.Owner) {
|
if (!permission || permission.type === WorkspaceRole.Owner) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,22 +513,22 @@ export class PermissionService {
|
|||||||
async checkCloudPagePermission(
|
async checkCloudPagePermission(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
pageId: string,
|
pageId: string,
|
||||||
userId?: string,
|
action: AllPossibleGraphQLDocActionsKeys,
|
||||||
permission = Permission.Read
|
userId?: string
|
||||||
) {
|
) {
|
||||||
const hasWorkspace = await this.hasWorkspace(workspaceId);
|
const hasWorkspace = await this.hasWorkspace(workspaceId);
|
||||||
if (hasWorkspace) {
|
if (hasWorkspace) {
|
||||||
await this.checkPagePermission(workspaceId, pageId, userId, permission);
|
await this.checkPagePermission(workspaceId, pageId, action, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkPagePermission(
|
async checkPagePermission(
|
||||||
ws: string,
|
ws: string,
|
||||||
page: string,
|
page: string,
|
||||||
user?: string,
|
action: AllPossibleGraphQLDocActionsKeys,
|
||||||
permission = Permission.Read
|
user?: string
|
||||||
) {
|
) {
|
||||||
if (!(await this.tryCheckPage(ws, page, user, permission))) {
|
if (!(await this.tryCheckPage(ws, page, action, user))) {
|
||||||
throw new DocAccessDenied({ spaceId: ws, docId: page });
|
throw new DocAccessDenied({ spaceId: ws, docId: page });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,11 +536,12 @@ export class PermissionService {
|
|||||||
async tryCheckPage(
|
async tryCheckPage(
|
||||||
ws: string,
|
ws: string,
|
||||||
page: string,
|
page: string,
|
||||||
user?: string,
|
action: AllPossibleGraphQLDocActionsKeys,
|
||||||
permission = Permission.Read
|
user?: string
|
||||||
) {
|
) {
|
||||||
|
const role = findMinimalDocRole(action);
|
||||||
// check whether page is public
|
// check whether page is public
|
||||||
if (permission === Permission.Read) {
|
if (action === 'Doc_Read') {
|
||||||
const count = await this.prisma.workspacePage.count({
|
const count = await this.prisma.workspacePage.count({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: ws,
|
workspaceId: ws,
|
||||||
@@ -541,7 +565,7 @@ export class PermissionService {
|
|||||||
userId: user,
|
userId: user,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
type: {
|
type: {
|
||||||
gte: permission,
|
gte: role,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -550,11 +574,23 @@ export class PermissionService {
|
|||||||
// accessible
|
// accessible
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
this.logger.log("User's PageRole is lower than required", {
|
||||||
|
workspaceId: ws,
|
||||||
|
pageId: page,
|
||||||
|
userId: user,
|
||||||
|
requiredRole: DocRole[role],
|
||||||
|
action,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether user has workspace related permission
|
// check whether user has workspace related permission
|
||||||
return this.tryCheckWorkspace(ws, user, permission);
|
return this.tryCheckWorkspace(
|
||||||
|
ws,
|
||||||
|
user,
|
||||||
|
requiredWorkspaceRoleByDocRole(role)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isPublicPage(ws: string, page: string) {
|
async isPublicPage(ws: string, page: string) {
|
||||||
@@ -613,8 +649,8 @@ export class PermissionService {
|
|||||||
ws: string,
|
ws: string,
|
||||||
page: string,
|
page: string,
|
||||||
user: string,
|
user: string,
|
||||||
permission: Permission = Permission.Read
|
permission: DocRole
|
||||||
) {
|
): Promise<string> {
|
||||||
const data = await this.prisma.workspacePageUserPermission.findFirst({
|
const data = await this.prisma.workspacePageUserPermission.findFirst({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: ws,
|
workspaceId: ws,
|
||||||
@@ -637,18 +673,18 @@ export class PermissionService {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// If the new permission is owner, we need to revoke old owner
|
// If the new permission is owner, we need to revoke old owner
|
||||||
permission === Permission.Owner
|
permission === DocRole.Owner
|
||||||
? this.prisma.workspacePageUserPermission.updateMany({
|
? this.prisma.workspacePageUserPermission.updateMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: ws,
|
workspaceId: ws,
|
||||||
pageId: page,
|
pageId: page,
|
||||||
type: Permission.Owner,
|
type: DocRole.Owner,
|
||||||
userId: {
|
userId: {
|
||||||
not: user,
|
not: user,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
type: Permission.Admin,
|
type: DocRole.Manager,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
@@ -670,20 +706,93 @@ export class PermissionService {
|
|||||||
.then(p => p.id);
|
.then(p => p.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async revokePage(ws: string, page: string, user: string) {
|
async revokePage(ws: string, page: string, users: string[]) {
|
||||||
const result = await this.prisma.workspacePageUserPermission.deleteMany({
|
const result = await this.prisma.workspacePageUserPermission.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: ws,
|
workspaceId: ws,
|
||||||
pageId: page,
|
pageId: page,
|
||||||
userId: user,
|
userId: {
|
||||||
|
in: users,
|
||||||
|
},
|
||||||
type: {
|
type: {
|
||||||
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading
|
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading
|
||||||
not: Permission.Owner,
|
not: DocRole.Owner,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.count > 0;
|
return result.count > 0;
|
||||||
}
|
}
|
||||||
/// End regin: page permission
|
|
||||||
|
async grantPagePermission(
|
||||||
|
workspaceId: string,
|
||||||
|
pageId: string,
|
||||||
|
userIds: string[],
|
||||||
|
role: DocRole
|
||||||
|
) {
|
||||||
|
if (userIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (role === DocRole.Owner) {
|
||||||
|
if (userIds.length > 1) {
|
||||||
|
throw new SpaceShouldHaveOnlyOneOwner({ spaceId: workspaceId });
|
||||||
|
}
|
||||||
|
return [await this.grantPage(workspaceId, pageId, userIds[0], role)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ret = await this.prisma.$transaction(async tx =>
|
||||||
|
Promise.all(
|
||||||
|
userIds.map(id =>
|
||||||
|
tx.workspacePageUserPermission.upsert({
|
||||||
|
where: {
|
||||||
|
workspaceId_pageId_userId: {
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
userId: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
userId: id,
|
||||||
|
type: role,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
type: role,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return ret.map(p => p.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePagePermission(
|
||||||
|
workspaceId: string,
|
||||||
|
pageId: string,
|
||||||
|
userId: string,
|
||||||
|
role: DocRole
|
||||||
|
) {
|
||||||
|
const permission = await this.prisma.workspacePageUserPermission.findFirst({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permission) {
|
||||||
|
return this.grantPage(workspaceId, pageId, userId, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = await this.prisma.workspacePageUserPermission.update({
|
||||||
|
where: {
|
||||||
|
id: permission.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: role,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,328 @@
|
|||||||
export enum Permission {
|
import assert from 'node:assert';
|
||||||
Read = 0,
|
|
||||||
Write = 1,
|
|
||||||
Admin = 10,
|
|
||||||
Owner = 99,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PublicPageMode {
|
export enum PublicPageMode {
|
||||||
Page,
|
Page,
|
||||||
Edgeless,
|
Edgeless,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DocRole {
|
||||||
|
External = 0,
|
||||||
|
Reader = 10,
|
||||||
|
Editor = 20,
|
||||||
|
Manager = 30,
|
||||||
|
Owner = 99,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WorkspaceRole {
|
||||||
|
External = -99,
|
||||||
|
Collaborator = 1,
|
||||||
|
Admin = 10,
|
||||||
|
Owner = 99,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Actions = {
|
||||||
|
Workspace: {
|
||||||
|
Sync: 1,
|
||||||
|
CreateDoc: 2,
|
||||||
|
Delete: 11,
|
||||||
|
TransferOwner: 12,
|
||||||
|
Organize: {
|
||||||
|
Read: 0,
|
||||||
|
},
|
||||||
|
Users: {
|
||||||
|
Read: 3,
|
||||||
|
Manage: 6,
|
||||||
|
},
|
||||||
|
Properties: {
|
||||||
|
Read: 4,
|
||||||
|
Create: 8,
|
||||||
|
Update: 9,
|
||||||
|
Delete: 10,
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
Read: 5,
|
||||||
|
Update: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Doc: {
|
||||||
|
Read: 13,
|
||||||
|
Copy: 14,
|
||||||
|
Duplicate: 17,
|
||||||
|
Trash: 18,
|
||||||
|
Restore: 19,
|
||||||
|
Delete: 20,
|
||||||
|
Update: 22,
|
||||||
|
Publish: 23,
|
||||||
|
TransferOwner: 25,
|
||||||
|
Properties: {
|
||||||
|
Read: 15,
|
||||||
|
Update: 21,
|
||||||
|
},
|
||||||
|
Users: {
|
||||||
|
Read: 16,
|
||||||
|
Manage: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type ActionsKeysUnion = typeof Actions extends {
|
||||||
|
[k in infer _K extends string]: infer _V;
|
||||||
|
}
|
||||||
|
? _V extends {
|
||||||
|
[k1 in infer _K1 extends string]: infer _V1;
|
||||||
|
}
|
||||||
|
? _V1 extends {
|
||||||
|
[k2 in infer _K2 extends string]: number;
|
||||||
|
}
|
||||||
|
? _K1 extends keyof (typeof Actions)[_K]
|
||||||
|
? _K2 extends keyof (typeof Actions)[_K][_K1]
|
||||||
|
? `${_K}.${_K1}.${_K2}`
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: _V1 extends number
|
||||||
|
? `${_K}.${_K1}`
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type ExcludeObjectKeys<
|
||||||
|
T,
|
||||||
|
Key extends keyof typeof Actions,
|
||||||
|
Split extends string,
|
||||||
|
> = T extends `${infer _K extends Key}.${infer _K1}.${infer _K2}`
|
||||||
|
? _K1 extends keyof (typeof Actions)[_K]
|
||||||
|
? _K2 extends keyof (typeof Actions)[_K][_K1]
|
||||||
|
? `${_K}${Split}${_K1}${Split}${_K2}`
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: T extends `${infer _K extends Key}.${infer _K1}`
|
||||||
|
? _K1 extends keyof (typeof Actions)[_K]
|
||||||
|
? (typeof Actions)[_K][_K1] extends number
|
||||||
|
? `${_K}${Split}${_K1}`
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type AllPossibleActionsKeys = ExcludeObjectKeys<
|
||||||
|
ActionsKeysUnion,
|
||||||
|
keyof typeof Actions,
|
||||||
|
'.'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type AllPossibleGraphQLWorkspaceActionsKeys = ExcludeObjectKeys<
|
||||||
|
ActionsKeysUnion,
|
||||||
|
'Workspace',
|
||||||
|
'_'
|
||||||
|
>;
|
||||||
|
export type AllPossibleGraphQLDocActionsKeys = ExcludeObjectKeys<
|
||||||
|
ActionsKeysUnion,
|
||||||
|
'Doc',
|
||||||
|
'_'
|
||||||
|
>;
|
||||||
|
|
||||||
|
type AllPossibleGraphQLActionsKeys =
|
||||||
|
| AllPossibleGraphQLWorkspaceActionsKeys
|
||||||
|
| AllPossibleGraphQLDocActionsKeys;
|
||||||
|
|
||||||
|
export const ActionsKeys: AllPossibleActionsKeys[] = [
|
||||||
|
'Workspace.Organize.Read',
|
||||||
|
'Workspace.Sync',
|
||||||
|
'Workspace.CreateDoc',
|
||||||
|
'Workspace.Users.Read',
|
||||||
|
'Workspace.Properties.Read',
|
||||||
|
'Workspace.Settings.Read',
|
||||||
|
'Workspace.Users.Manage',
|
||||||
|
'Workspace.Settings.Update',
|
||||||
|
'Workspace.Properties.Create',
|
||||||
|
'Workspace.Properties.Update',
|
||||||
|
'Workspace.Properties.Delete',
|
||||||
|
'Workspace.Delete',
|
||||||
|
'Workspace.TransferOwner',
|
||||||
|
'Doc.Read',
|
||||||
|
'Doc.Copy',
|
||||||
|
'Doc.Properties.Read',
|
||||||
|
'Doc.Users.Read',
|
||||||
|
'Doc.Duplicate',
|
||||||
|
'Doc.Trash',
|
||||||
|
'Doc.Restore',
|
||||||
|
'Doc.Delete',
|
||||||
|
'Doc.Properties.Update',
|
||||||
|
'Doc.Update',
|
||||||
|
'Doc.Publish',
|
||||||
|
'Doc.Users.Manage',
|
||||||
|
'Doc.TransferOwner',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
assert(
|
||||||
|
ActionsKeys.length === Actions.Doc.TransferOwner + 1,
|
||||||
|
'ActionsKeys length is not correct'
|
||||||
|
);
|
||||||
|
|
||||||
|
function permissionKeyToGraphQLKey(key: string) {
|
||||||
|
const k = key.split('.');
|
||||||
|
return k.join('_') as keyof PermissionsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultActionsMap = Object.fromEntries(
|
||||||
|
ActionsKeys.map(key => [permissionKeyToGraphQLKey(key), false])
|
||||||
|
) as PermissionsList;
|
||||||
|
|
||||||
|
export type WorkspacePermissionsList = {
|
||||||
|
[k in AllPossibleGraphQLWorkspaceActionsKeys]: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PermissionsList = {
|
||||||
|
[key in AllPossibleGraphQLActionsKeys]: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function mapWorkspaceRoleToWorkspaceActions(
|
||||||
|
workspaceRole: WorkspaceRole
|
||||||
|
) {
|
||||||
|
const permissionList = { ...DefaultActionsMap };
|
||||||
|
(RoleActionsMap.WorkspaceRole[workspaceRole] ?? []).forEach(action => {
|
||||||
|
permissionList[permissionKeyToGraphQLKey(ActionsKeys[action])] = true;
|
||||||
|
});
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(permissionList).filter(([k, _]) =>
|
||||||
|
k.startsWith('Workspace_')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapRoleToActions(
|
||||||
|
workspaceRole?: WorkspaceRole,
|
||||||
|
docRole?: DocRole
|
||||||
|
) {
|
||||||
|
const workspaceActions = workspaceRole
|
||||||
|
? (RoleActionsMap.WorkspaceRole[workspaceRole] ?? [])
|
||||||
|
: [];
|
||||||
|
const docActions = (function () {
|
||||||
|
// Doc owner/manager permission can not be overridden by workspace role
|
||||||
|
if (docRole !== undefined && docRole >= DocRole.Manager) {
|
||||||
|
return RoleActionsMap.DocRole[docRole];
|
||||||
|
}
|
||||||
|
switch (workspaceRole) {
|
||||||
|
case WorkspaceRole.Admin:
|
||||||
|
case WorkspaceRole.Owner:
|
||||||
|
return RoleActionsMap.DocRole[DocRole.Manager];
|
||||||
|
case WorkspaceRole.Collaborator:
|
||||||
|
return RoleActionsMap.DocRole[DocRole.Editor];
|
||||||
|
default:
|
||||||
|
return docRole !== undefined
|
||||||
|
? (RoleActionsMap.DocRole[docRole] ?? [])
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const permissionList = { ...DefaultActionsMap };
|
||||||
|
[...workspaceActions, ...docActions].forEach(action => {
|
||||||
|
permissionList[permissionKeyToGraphQLKey(ActionsKeys[action])] = true;
|
||||||
|
});
|
||||||
|
return permissionList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findMinimalDocRole(
|
||||||
|
action: AllPossibleGraphQLDocActionsKeys
|
||||||
|
): DocRole {
|
||||||
|
const [_, actionKey, actionKey2] = action.split('_');
|
||||||
|
|
||||||
|
const actionValue: number = actionKey2
|
||||||
|
? // @ts-expect-error Actions[actionKey] exists
|
||||||
|
Actions.Doc[actionKey][actionKey2]
|
||||||
|
: // @ts-expect-error Actions[actionKey] exists
|
||||||
|
Actions.Doc[actionKey];
|
||||||
|
if (actionValue <= Actions.Doc.Properties.Read) {
|
||||||
|
return DocRole.External;
|
||||||
|
}
|
||||||
|
if (actionValue <= Actions.Doc.Duplicate) {
|
||||||
|
return DocRole.Reader;
|
||||||
|
}
|
||||||
|
if (actionValue <= Actions.Doc.Update) {
|
||||||
|
return DocRole.Editor;
|
||||||
|
}
|
||||||
|
if (actionValue <= Actions.Doc.Users.Manage) {
|
||||||
|
return DocRole.Manager;
|
||||||
|
}
|
||||||
|
return DocRole.Owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requiredWorkspaceRoleByDocRole(
|
||||||
|
docRole: DocRole
|
||||||
|
): WorkspaceRole {
|
||||||
|
switch (docRole) {
|
||||||
|
case DocRole.Owner:
|
||||||
|
return WorkspaceRole.Owner;
|
||||||
|
case DocRole.Manager:
|
||||||
|
return WorkspaceRole.Admin;
|
||||||
|
case DocRole.Editor:
|
||||||
|
case DocRole.Reader:
|
||||||
|
case DocRole.External:
|
||||||
|
return WorkspaceRole.Collaborator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RoleActionsMap = {
|
||||||
|
WorkspaceRole: {
|
||||||
|
get [WorkspaceRole.External]() {
|
||||||
|
return [Actions.Workspace.Organize.Read];
|
||||||
|
},
|
||||||
|
get [WorkspaceRole.Collaborator]() {
|
||||||
|
return [
|
||||||
|
...this[WorkspaceRole.External],
|
||||||
|
Actions.Workspace.Sync,
|
||||||
|
Actions.Workspace.CreateDoc,
|
||||||
|
Actions.Workspace.Users.Read,
|
||||||
|
Actions.Workspace.Properties.Read,
|
||||||
|
Actions.Workspace.Settings.Read,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
get [WorkspaceRole.Admin]() {
|
||||||
|
return [
|
||||||
|
...this[WorkspaceRole.Collaborator],
|
||||||
|
Actions.Workspace.Users.Manage,
|
||||||
|
Actions.Workspace.Settings.Update,
|
||||||
|
Actions.Workspace.Properties.Create,
|
||||||
|
Actions.Workspace.Properties.Update,
|
||||||
|
Actions.Workspace.Properties.Delete,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
get [WorkspaceRole.Owner]() {
|
||||||
|
return [
|
||||||
|
...this[WorkspaceRole.Admin],
|
||||||
|
Actions.Workspace.Delete,
|
||||||
|
Actions.Workspace.TransferOwner,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DocRole: {
|
||||||
|
get [DocRole.External]() {
|
||||||
|
return [Actions.Doc.Read, Actions.Doc.Copy, Actions.Doc.Properties.Read];
|
||||||
|
},
|
||||||
|
get [DocRole.Reader]() {
|
||||||
|
return [
|
||||||
|
...this[DocRole.External],
|
||||||
|
Actions.Doc.Users.Read,
|
||||||
|
Actions.Doc.Duplicate,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
get [DocRole.Editor]() {
|
||||||
|
return [
|
||||||
|
...this[DocRole.Reader],
|
||||||
|
Actions.Doc.Trash,
|
||||||
|
Actions.Doc.Restore,
|
||||||
|
Actions.Doc.Delete,
|
||||||
|
Actions.Doc.Properties.Update,
|
||||||
|
Actions.Doc.Update,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
get [DocRole.Manager]() {
|
||||||
|
return [
|
||||||
|
...this[DocRole.Editor],
|
||||||
|
Actions.Doc.Publish,
|
||||||
|
Actions.Doc.Users.Manage,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
get [DocRole.Owner]() {
|
||||||
|
return [...this[DocRole.Manager], Actions.Doc.TransferOwner];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
PgUserspaceDocStorageAdapter,
|
PgUserspaceDocStorageAdapter,
|
||||||
PgWorkspaceDocStorageAdapter,
|
PgWorkspaceDocStorageAdapter,
|
||||||
} from '../doc';
|
} from '../doc';
|
||||||
import { Permission, PermissionService } from '../permission';
|
import { PermissionService, WorkspaceRole } from '../permission';
|
||||||
import { DocID } from '../utils/doc';
|
import { DocID } from '../utils/doc';
|
||||||
|
|
||||||
const SubscribeMessage = (event: string) =>
|
const SubscribeMessage = (event: string) =>
|
||||||
@@ -615,7 +615,7 @@ abstract class SyncSocketAdapter {
|
|||||||
|
|
||||||
async join(userId: string, spaceId: string, roomType: RoomType = 'sync') {
|
async join(userId: string, spaceId: string, roomType: RoomType = 'sync') {
|
||||||
this.assertNotIn(spaceId, roomType);
|
this.assertNotIn(spaceId, roomType);
|
||||||
await this.assertAccessible(spaceId, userId, Permission.Read);
|
await this.assertAccessible(spaceId, userId, WorkspaceRole.Collaborator);
|
||||||
return this.client.join(this.room(spaceId, roomType));
|
return this.client.join(this.room(spaceId, roomType));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,7 +643,7 @@ abstract class SyncSocketAdapter {
|
|||||||
abstract assertAccessible(
|
abstract assertAccessible(
|
||||||
spaceId: string,
|
spaceId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
permission?: Permission
|
permission?: WorkspaceRole
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
push(spaceId: string, docId: string, updates: Buffer[], editorId: string) {
|
push(spaceId: string, docId: string, updates: Buffer[], editorId: string) {
|
||||||
@@ -694,7 +694,7 @@ class WorkspaceSyncAdapter extends SyncSocketAdapter {
|
|||||||
async assertAccessible(
|
async assertAccessible(
|
||||||
spaceId: string,
|
spaceId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
!(await this.permission.isWorkspaceMember(spaceId, userId, permission))
|
!(await this.permission.isWorkspaceMember(spaceId, userId, permission))
|
||||||
@@ -712,7 +712,7 @@ class UserspaceSyncAdapter extends SyncSocketAdapter {
|
|||||||
async assertAccessible(
|
async assertAccessible(
|
||||||
spaceId: string,
|
spaceId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
_permission: Permission = Permission.Read
|
_permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
if (spaceId !== userId) {
|
if (spaceId !== userId) {
|
||||||
throw new SpaceAccessDenied({ spaceId });
|
throw new SpaceAccessDenied({ spaceId });
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '../../base';
|
} from '../../base';
|
||||||
import { CurrentUser, Public } from '../auth';
|
import { CurrentUser, Public } from '../auth';
|
||||||
import { PgWorkspaceDocStorageAdapter } from '../doc';
|
import { PgWorkspaceDocStorageAdapter } from '../doc';
|
||||||
import { Permission, PermissionService, PublicPageMode } from '../permission';
|
import { PermissionService, PublicPageMode } from '../permission';
|
||||||
import { WorkspaceBlobStorage } from '../storage';
|
import { WorkspaceBlobStorage } from '../storage';
|
||||||
import { DocID } from '../utils/doc';
|
import { DocID } from '../utils/doc';
|
||||||
|
|
||||||
@@ -147,8 +147,8 @@ export class WorkspacesController {
|
|||||||
await this.permission.checkPagePermission(
|
await this.permission.checkPagePermission(
|
||||||
docId.workspace,
|
docId.workspace,
|
||||||
docId.guid,
|
docId.guid,
|
||||||
user.id,
|
'Doc_Read',
|
||||||
Permission.Write
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const history = await this.workspace.getDocHistory(
|
const history = await this.workspace.getDocHistory(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { CurrentUser } from '../auth';
|
|||||||
import { Admin } from '../common';
|
import { Admin } from '../common';
|
||||||
import { FeatureManagementService, FeatureType } from '../features';
|
import { FeatureManagementService, FeatureType } from '../features';
|
||||||
import { PermissionService } from '../permission';
|
import { PermissionService } from '../permission';
|
||||||
import { WorkspaceType } from './types';
|
import { WorkspaceFeatureType, WorkspaceType } from './types';
|
||||||
|
|
||||||
@Resolver(() => WorkspaceType)
|
@Resolver(() => WorkspaceType)
|
||||||
export class WorkspaceManagementResolver {
|
export class WorkspaceManagementResolver {
|
||||||
@@ -41,10 +41,10 @@ export class WorkspaceManagementResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Admin()
|
@Admin()
|
||||||
@Query(() => [WorkspaceType])
|
@Query(() => [WorkspaceFeatureType])
|
||||||
async listWorkspaceFeatures(
|
async listWorkspaceFeatures(
|
||||||
@Args('feature', { type: () => FeatureType }) feature: FeatureType
|
@Args('feature', { type: () => FeatureType }) feature: FeatureType
|
||||||
): Promise<WorkspaceType[]> {
|
): Promise<WorkspaceFeatureType[]> {
|
||||||
return this.feature.listFeatureWorkspaces(feature);
|
return this.feature.listFeatureWorkspaces(feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
|||||||
import type { FileUpload } from '../../../base';
|
import type { FileUpload } from '../../../base';
|
||||||
import { BlobQuotaExceeded, CloudThrottlerGuard } from '../../../base';
|
import { BlobQuotaExceeded, CloudThrottlerGuard } from '../../../base';
|
||||||
import { CurrentUser } from '../../auth';
|
import { CurrentUser } from '../../auth';
|
||||||
import { Permission, PermissionService } from '../../permission';
|
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||||
import { QuotaManagementService } from '../../quota';
|
import { QuotaManagementService } from '../../quota';
|
||||||
import { WorkspaceBlobStorage } from '../../storage';
|
import { WorkspaceBlobStorage } from '../../storage';
|
||||||
import { WorkspaceBlobSizes, WorkspaceType } from '../types';
|
import { WorkspaceBlobSizes, WorkspaceType } from '../types';
|
||||||
@@ -102,7 +102,7 @@ export class WorkspaceBlobResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
|
|
||||||
const checkExceeded =
|
const checkExceeded =
|
||||||
@@ -174,7 +174,7 @@ export class WorkspaceBlobResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.storage.release(workspaceId);
|
await this.storage.release(workspaceId);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type { SnapshotHistory } from '@prisma/client';
|
|||||||
|
|
||||||
import { CurrentUser } from '../../auth';
|
import { CurrentUser } from '../../auth';
|
||||||
import { PgWorkspaceDocStorageAdapter } from '../../doc';
|
import { PgWorkspaceDocStorageAdapter } from '../../doc';
|
||||||
import { Permission, PermissionService } from '../../permission';
|
import { PermissionService } from '../../permission';
|
||||||
import { DocID } from '../../utils/doc';
|
import { DocID } from '../../utils/doc';
|
||||||
import { WorkspaceType } from '../types';
|
import { WorkspaceType } from '../types';
|
||||||
import { EditorType } from './workspace';
|
import { EditorType } from './workspace';
|
||||||
@@ -79,8 +79,8 @@ export class DocHistoryResolver {
|
|||||||
await this.permission.checkPagePermission(
|
await this.permission.checkPagePermission(
|
||||||
docId.workspace,
|
docId.workspace,
|
||||||
docId.guid,
|
docId.guid,
|
||||||
user.id,
|
'Doc_Restore',
|
||||||
Permission.Write
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.workspace.rollbackDoc(
|
await this.workspace.rollbackDoc(
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { Logger } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
Args,
|
Args,
|
||||||
Field,
|
Field,
|
||||||
|
InputType,
|
||||||
|
Int,
|
||||||
Mutation,
|
Mutation,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Parent,
|
Parent,
|
||||||
@@ -12,18 +15,25 @@ import type { WorkspacePage as PrismaWorkspacePage } from '@prisma/client';
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ExpectToGrantDocUserRoles,
|
||||||
ExpectToPublishPage,
|
ExpectToPublishPage,
|
||||||
|
ExpectToRevokeDocUserRoles,
|
||||||
ExpectToRevokePublicPage,
|
ExpectToRevokePublicPage,
|
||||||
|
ExpectToUpdateDocUserRole,
|
||||||
PageIsNotPublic,
|
PageIsNotPublic,
|
||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { CurrentUser } from '../../auth';
|
import { CurrentUser } from '../../auth';
|
||||||
import {
|
import {
|
||||||
Permission,
|
DocRole,
|
||||||
PermissionService,
|
PermissionService,
|
||||||
PublicPageMode,
|
PublicPageMode,
|
||||||
|
WorkspaceRole,
|
||||||
} from '../../permission';
|
} from '../../permission';
|
||||||
|
import { mapRoleToActions, PermissionsList } from '../../permission/types';
|
||||||
|
import { UserType } from '../../user';
|
||||||
import { DocID } from '../../utils/doc';
|
import { DocID } from '../../utils/doc';
|
||||||
import { WorkspaceType } from '../types';
|
import { WorkspaceType } from '../types';
|
||||||
|
import { WorkspacePermissions } from './workspace';
|
||||||
|
|
||||||
registerEnumType(PublicPageMode, {
|
registerEnumType(PublicPageMode, {
|
||||||
name: 'PublicPageMode',
|
name: 'PublicPageMode',
|
||||||
@@ -45,8 +55,133 @@ class WorkspacePage implements Partial<PrismaWorkspacePage> {
|
|||||||
public!: boolean;
|
public!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
class GrantDocUserRolesInput {
|
||||||
|
@Field(() => String)
|
||||||
|
docId!: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
workspaceId!: string;
|
||||||
|
|
||||||
|
@Field(() => DocRole)
|
||||||
|
role!: DocRole;
|
||||||
|
|
||||||
|
@Field(() => [String])
|
||||||
|
userIds!: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
class PageGrantedUsersInput {
|
||||||
|
@Field(() => Int)
|
||||||
|
first!: number;
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
offset?: number;
|
||||||
|
|
||||||
|
@Field(() => String, { description: 'Cursor', nullable: true })
|
||||||
|
after?: string;
|
||||||
|
|
||||||
|
@Field(() => String, { description: 'Cursor', nullable: true })
|
||||||
|
before?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
class GrantedDocUserType {
|
||||||
|
@Field(() => UserType)
|
||||||
|
user!: UserType;
|
||||||
|
|
||||||
|
@Field(() => DocRole)
|
||||||
|
role!: DocRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
class PageInfo {
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
startCursor?: string;
|
||||||
|
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
endCursor?: string;
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
hasNextPage!: boolean;
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
hasPreviousPage!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
class GrantedDocUserEdge {
|
||||||
|
@Field(() => GrantedDocUserType)
|
||||||
|
user!: GrantedDocUserType;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
cursor!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
class GrantedDocUsersConnection {
|
||||||
|
@Field(() => Int)
|
||||||
|
totalCount!: number;
|
||||||
|
|
||||||
|
@Field(() => [GrantedDocUserEdge])
|
||||||
|
edges!: GrantedDocUserEdge[];
|
||||||
|
|
||||||
|
@Field(() => PageInfo)
|
||||||
|
pageInfo!: PageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class RolePermissions
|
||||||
|
extends WorkspacePermissions
|
||||||
|
implements PermissionsList
|
||||||
|
{
|
||||||
|
@Field()
|
||||||
|
Doc_Read!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Copy!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Properties_Read!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Users_Read!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Duplicate!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Trash!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Restore!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Delete!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Properties_Update!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Update!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Publish!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_Users_Manage!: boolean;
|
||||||
|
@Field()
|
||||||
|
Doc_TransferOwner!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
class DocType {
|
||||||
|
@Field(() => String)
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
public!: boolean;
|
||||||
|
|
||||||
|
@Field(() => DocRole)
|
||||||
|
role!: DocRole;
|
||||||
|
|
||||||
|
@Field(() => RolePermissions)
|
||||||
|
permissions!: RolePermissions;
|
||||||
|
}
|
||||||
|
|
||||||
@Resolver(() => WorkspaceType)
|
@Resolver(() => WorkspaceType)
|
||||||
export class PagePermissionResolver {
|
export class PagePermissionResolver {
|
||||||
|
private readonly logger = new Logger(PagePermissionResolver.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaClient,
|
private readonly prisma: PrismaClient,
|
||||||
private readonly permission: PermissionService
|
private readonly permission: PermissionService
|
||||||
@@ -102,6 +237,122 @@ export class PagePermissionResolver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [permission, workspacePermission] = await this.prisma.$transaction(
|
||||||
|
tx =>
|
||||||
|
Promise.all([
|
||||||
|
tx.workspacePageUserPermission.findFirst({
|
||||||
|
where: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
pageId,
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
tx.workspaceUserPermission.findFirst({
|
||||||
|
where: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: pageId,
|
||||||
|
public: page?.public ?? false,
|
||||||
|
role: permission?.type ?? DocRole.External,
|
||||||
|
permissions: mapRoleToActions(
|
||||||
|
workspacePermission?.type,
|
||||||
|
permission?.type
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => GrantedDocUsersConnection, {
|
||||||
|
description: 'Page granted users list',
|
||||||
|
complexity: 4,
|
||||||
|
})
|
||||||
|
async pageGrantedUsersList(
|
||||||
|
@Parent() workspace: WorkspaceType,
|
||||||
|
@Args('pageId') pageId: string,
|
||||||
|
@Args('pageGrantedUsersInput')
|
||||||
|
pageGrantedUsersInput: PageGrantedUsersInput
|
||||||
|
): Promise<GrantedDocUsersConnection> {
|
||||||
|
const docId = new DocID(pageId, workspace.id);
|
||||||
|
const [permissions, totalCount] = await this.prisma.$transaction(tx => {
|
||||||
|
return Promise.all([
|
||||||
|
tx.workspacePageUserPermission.findMany({
|
||||||
|
where: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
pageId: docId.guid,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
take: pageGrantedUsersInput.first,
|
||||||
|
skip: pageGrantedUsersInput.offset,
|
||||||
|
cursor: pageGrantedUsersInput.after
|
||||||
|
? {
|
||||||
|
id: pageGrantedUsersInput.after,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
tx.workspacePageUserPermission.count({
|
||||||
|
where: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
pageId: docId.guid,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalCount,
|
||||||
|
edges: permissions.map(permission => ({
|
||||||
|
user: {
|
||||||
|
user: {
|
||||||
|
id: permission.user.id,
|
||||||
|
name: permission.user.name,
|
||||||
|
email: permission.user.email,
|
||||||
|
avatarUrl: permission.user.avatarUrl,
|
||||||
|
emailVerified: permission.user.emailVerifiedAt !== null,
|
||||||
|
hasPassword: permission.user.password !== null,
|
||||||
|
},
|
||||||
|
role: permission.type,
|
||||||
|
},
|
||||||
|
cursor: permission.id,
|
||||||
|
})),
|
||||||
|
pageInfo: {
|
||||||
|
startCursor: permissions.at(0)?.id,
|
||||||
|
endCursor: permissions.at(-1)?.id,
|
||||||
|
hasNextPage: totalCount > pageGrantedUsersInput.first,
|
||||||
|
hasPreviousPage:
|
||||||
|
pageGrantedUsersInput.offset !== undefined &&
|
||||||
|
pageGrantedUsersInput.offset > 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
@@ -134,15 +385,26 @@ export class PagePermissionResolver {
|
|||||||
const docId = new DocID(pageId, workspaceId);
|
const docId = new DocID(pageId, workspaceId);
|
||||||
|
|
||||||
if (docId.isWorkspace) {
|
if (docId.isWorkspace) {
|
||||||
|
this.logger.error('Expect to publish page, but it is a workspace', {
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
});
|
||||||
throw new ExpectToPublishPage();
|
throw new ExpectToPublishPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.permission.checkWorkspace(
|
await this.permission.checkPagePermission(
|
||||||
docId.workspace,
|
docId.workspace,
|
||||||
user.id,
|
docId.guid,
|
||||||
Permission.Write
|
'Doc_Publish',
|
||||||
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.logger.log('Publish page', {
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
mode,
|
||||||
|
});
|
||||||
|
|
||||||
return this.permission.publishPage(docId.workspace, docId.guid, mode);
|
return this.permission.publishPage(docId.workspace, docId.guid, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,13 +433,18 @@ export class PagePermissionResolver {
|
|||||||
const docId = new DocID(pageId, workspaceId);
|
const docId = new DocID(pageId, workspaceId);
|
||||||
|
|
||||||
if (docId.isWorkspace) {
|
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');
|
throw new ExpectToRevokePublicPage('Expect page not to be workspace');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.permission.checkWorkspace(
|
await this.permission.checkPagePermission(
|
||||||
docId.workspace,
|
docId.workspace,
|
||||||
user.id,
|
docId.guid,
|
||||||
Permission.Write
|
'Doc_Publish',
|
||||||
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const isPublic = await this.permission.isPublicPage(
|
const isPublic = await this.permission.isPublicPage(
|
||||||
@@ -186,9 +453,148 @@ export class PagePermissionResolver {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isPublic) {
|
if (!isPublic) {
|
||||||
|
this.logger.log('Expect to revoke public page, but it is not public', {
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
});
|
||||||
throw new PageIsNotPublic('Page is not public');
|
throw new PageIsNotPublic('Page is not public');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log('Revoke public page', {
|
||||||
|
workspaceId,
|
||||||
|
pageId,
|
||||||
|
});
|
||||||
|
|
||||||
return this.permission.revokePublicPage(docId.workspace, docId.guid);
|
return this.permission.revokePublicPage(docId.workspace, docId.guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
async grantDocUserRoles(
|
||||||
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args('input') input: GrantDocUserRolesInput
|
||||||
|
): Promise<boolean> {
|
||||||
|
const doc = new DocID(input.docId, input.workspaceId);
|
||||||
|
const pairs = {
|
||||||
|
spaceId: input.workspaceId,
|
||||||
|
docId: input.docId,
|
||||||
|
};
|
||||||
|
if (doc.isWorkspace) {
|
||||||
|
this.logger.error(
|
||||||
|
'Expect to grant doc user roles, but it is a workspace',
|
||||||
|
pairs
|
||||||
|
);
|
||||||
|
throw new ExpectToGrantDocUserRoles(
|
||||||
|
pairs,
|
||||||
|
'Expect doc not to be workspace'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.permission.checkPagePermission(
|
||||||
|
doc.workspace,
|
||||||
|
doc.guid,
|
||||||
|
'Doc_Users_Manage',
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
await this.permission.grantPagePermission(
|
||||||
|
doc.workspace,
|
||||||
|
doc.guid,
|
||||||
|
input.userIds,
|
||||||
|
input.role
|
||||||
|
);
|
||||||
|
this.logger.log('Grant doc user roles', {
|
||||||
|
...pairs,
|
||||||
|
userIds: input.userIds,
|
||||||
|
role: input.role,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
async revokeDocUserRoles(
|
||||||
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args('docId') docId: string,
|
||||||
|
@Args('userIds', { type: () => [String] }) userIds: string[]
|
||||||
|
): Promise<boolean> {
|
||||||
|
const doc = new DocID(docId);
|
||||||
|
const pairs = {
|
||||||
|
spaceId: doc.workspace,
|
||||||
|
docId: doc.guid,
|
||||||
|
};
|
||||||
|
if (doc.isWorkspace) {
|
||||||
|
this.logger.error(
|
||||||
|
'Expect to revoke doc user roles, but it is a workspace',
|
||||||
|
pairs
|
||||||
|
);
|
||||||
|
throw new ExpectToRevokeDocUserRoles(
|
||||||
|
pairs,
|
||||||
|
'Expect doc not to be workspace'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.permission.checkWorkspace(
|
||||||
|
doc.workspace,
|
||||||
|
user.id,
|
||||||
|
WorkspaceRole.Collaborator
|
||||||
|
);
|
||||||
|
await this.permission.revokePage(doc.workspace, doc.guid, userIds);
|
||||||
|
this.logger.log('Revoke doc user roles', {
|
||||||
|
...pairs,
|
||||||
|
userIds: userIds,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
async updateDocUserRole(
|
||||||
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args('docId') docId: string,
|
||||||
|
@Args('userId') userId: string,
|
||||||
|
@Args('role', { type: () => DocRole }) role: DocRole
|
||||||
|
): Promise<boolean> {
|
||||||
|
const doc = new DocID(docId);
|
||||||
|
const pairs = {
|
||||||
|
spaceId: doc.workspace,
|
||||||
|
docId: doc.guid,
|
||||||
|
};
|
||||||
|
if (doc.isWorkspace) {
|
||||||
|
this.logger.error(
|
||||||
|
'Expect to update doc user role, but it is a workspace',
|
||||||
|
pairs
|
||||||
|
);
|
||||||
|
throw new ExpectToUpdateDocUserRole(
|
||||||
|
pairs,
|
||||||
|
'Expect doc not to be workspace'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.permission.checkWorkspace(
|
||||||
|
doc.workspace,
|
||||||
|
user.id,
|
||||||
|
WorkspaceRole.Collaborator
|
||||||
|
);
|
||||||
|
if (role === DocRole.Owner) {
|
||||||
|
const ret = await this.permission.grantPagePermission(
|
||||||
|
doc.workspace,
|
||||||
|
doc.guid,
|
||||||
|
[userId],
|
||||||
|
role
|
||||||
|
);
|
||||||
|
this.logger.log('Transfer doc owner', {
|
||||||
|
...pairs,
|
||||||
|
userId: userId,
|
||||||
|
role: role,
|
||||||
|
});
|
||||||
|
return ret.length > 0;
|
||||||
|
} else {
|
||||||
|
await this.permission.updatePagePermission(
|
||||||
|
doc.workspace,
|
||||||
|
doc.guid,
|
||||||
|
userId,
|
||||||
|
role
|
||||||
|
);
|
||||||
|
this.logger.log('Update doc user role', {
|
||||||
|
...pairs,
|
||||||
|
userId: userId,
|
||||||
|
role: role,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { Models } from '../../../models';
|
import { Models } from '../../../models';
|
||||||
import { DocContentService } from '../../doc-renderer';
|
import { DocContentService } from '../../doc-renderer';
|
||||||
import { Permission, PermissionService } from '../../permission';
|
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||||
import { WorkspaceBlobStorage } from '../../storage';
|
import { WorkspaceBlobStorage } from '../../storage';
|
||||||
|
|
||||||
export const defaultWorkspaceAvatar =
|
export const defaultWorkspaceAvatar =
|
||||||
@@ -221,14 +221,14 @@ export class WorkspaceService {
|
|||||||
|
|
||||||
async sendRoleChangedEmail(
|
async sendRoleChangedEmail(
|
||||||
userId: string,
|
userId: string,
|
||||||
ws: { id: string; role: Permission }
|
ws: { id: string; role: WorkspaceRole }
|
||||||
) {
|
) {
|
||||||
const user = await this.models.user.getPublicUser(userId);
|
const user = await this.models.user.getPublicUser(userId);
|
||||||
if (!user) throw new UserNotFound();
|
if (!user) throw new UserNotFound();
|
||||||
|
|
||||||
const workspace = await this.getWorkspaceInfo(ws.id);
|
const workspace = await this.getWorkspaceInfo(ws.id);
|
||||||
|
|
||||||
if (ws.role === Permission.Admin) {
|
if (ws.role === WorkspaceRole.Admin) {
|
||||||
await this.mailer.sendTeamBecomeAdminMail(user.email, {
|
await this.mailer.sendTeamBecomeAdminMail(user.email, {
|
||||||
workspace,
|
workspace,
|
||||||
url: this.url.link(`/workspace/${workspace.id}`),
|
url: this.url.link(`/workspace/${workspace.id}`),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from '../../../base';
|
} from '../../../base';
|
||||||
import { Models } from '../../../models';
|
import { Models } from '../../../models';
|
||||||
import { CurrentUser } from '../../auth';
|
import { CurrentUser } from '../../auth';
|
||||||
import { Permission, PermissionService } from '../../permission';
|
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||||
import { QuotaManagementService } from '../../quota';
|
import { QuotaManagementService } from '../../quota';
|
||||||
import {
|
import {
|
||||||
InviteLink,
|
InviteLink,
|
||||||
@@ -71,7 +71,7 @@ export class TeamWorkspaceResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
|
|
||||||
if (emails.length > 512) {
|
if (emails.length > 512) {
|
||||||
@@ -113,7 +113,7 @@ export class TeamWorkspaceResolver {
|
|||||||
ret.inviteId = await this.permissions.grant(
|
ret.inviteId = await this.permissions.grant(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
target.id,
|
target.id,
|
||||||
Permission.Write,
|
WorkspaceRole.Collaborator,
|
||||||
needMoreSeat
|
needMoreSeat
|
||||||
? WorkspaceMemberStatus.NeedMoreSeat
|
? WorkspaceMemberStatus.NeedMoreSeat
|
||||||
: WorkspaceMemberStatus.Pending
|
: WorkspaceMemberStatus.Pending
|
||||||
@@ -159,7 +159,7 @@ export class TeamWorkspaceResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
|
|
||||||
const cacheId = `workspace:inviteLink:${workspace.id}`;
|
const cacheId = `workspace:inviteLink:${workspace.id}`;
|
||||||
@@ -186,7 +186,7 @@ export class TeamWorkspaceResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
const cacheWorkspaceId = `workspace:inviteLink:${workspaceId}`;
|
const cacheWorkspaceId = `workspace:inviteLink:${workspaceId}`;
|
||||||
const invite = await this.cache.get<{ inviteId: string }>(cacheWorkspaceId);
|
const invite = await this.cache.get<{ inviteId: string }>(cacheWorkspaceId);
|
||||||
@@ -222,7 +222,7 @@ export class TeamWorkspaceResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
const cacheId = `workspace:inviteLink:${workspaceId}`;
|
const cacheId = `workspace:inviteLink:${workspaceId}`;
|
||||||
return await this.cache.delete(cacheId);
|
return await this.cache.delete(cacheId);
|
||||||
@@ -237,7 +237,7 @@ export class TeamWorkspaceResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -257,7 +257,7 @@ export class TeamWorkspaceResolver {
|
|||||||
const result = await this.permissions.grant(
|
const result = await this.permissions.grant(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
userId,
|
userId,
|
||||||
Permission.Write,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -283,12 +283,12 @@ export class TeamWorkspaceResolver {
|
|||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
@Args('workspaceId') workspaceId: string,
|
@Args('workspaceId') workspaceId: string,
|
||||||
@Args('userId') userId: string,
|
@Args('userId') userId: string,
|
||||||
@Args('permission', { type: () => Permission }) permission: Permission
|
@Args('permission', { type: () => WorkspaceRole }) permission: WorkspaceRole
|
||||||
) {
|
) {
|
||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -311,7 +311,7 @@ export class TeamWorkspaceResolver {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (permission === Permission.Owner) {
|
if (permission === WorkspaceRole.Owner) {
|
||||||
this.event.emit('workspace.members.ownershipTransferred', {
|
this.event.emit('workspace.members.ownershipTransferred', {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
from: user.id,
|
from: user.id,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Logger } from '@nestjs/common';
|
|
||||||
import {
|
import {
|
||||||
Args,
|
Args,
|
||||||
Field,
|
Field,
|
||||||
@@ -15,6 +14,7 @@ import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
|||||||
|
|
||||||
import type { FileUpload } from '../../../base';
|
import type { FileUpload } from '../../../base';
|
||||||
import {
|
import {
|
||||||
|
AFFiNELogger,
|
||||||
AlreadyInSpace,
|
AlreadyInSpace,
|
||||||
Cache,
|
Cache,
|
||||||
DocNotFound,
|
DocNotFound,
|
||||||
@@ -33,7 +33,11 @@ import {
|
|||||||
import { Models } from '../../../models';
|
import { Models } from '../../../models';
|
||||||
import { CurrentUser, Public } from '../../auth';
|
import { CurrentUser, Public } from '../../auth';
|
||||||
import { type Editor, PgWorkspaceDocStorageAdapter } from '../../doc';
|
import { type Editor, PgWorkspaceDocStorageAdapter } from '../../doc';
|
||||||
import { Permission, PermissionService } from '../../permission';
|
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||||
|
import {
|
||||||
|
mapWorkspaceRoleToWorkspaceActions,
|
||||||
|
WorkspacePermissionsList,
|
||||||
|
} from '../../permission/types';
|
||||||
import { QuotaManagementService, QuotaQueryType } from '../../quota';
|
import { QuotaManagementService, QuotaQueryType } from '../../quota';
|
||||||
import { UserType } from '../../user';
|
import { UserType } from '../../user';
|
||||||
import {
|
import {
|
||||||
@@ -68,6 +72,45 @@ class WorkspacePageMeta {
|
|||||||
updatedBy!: EditorType | null;
|
updatedBy!: EditorType | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class WorkspacePermissions implements WorkspacePermissionsList {
|
||||||
|
@Field()
|
||||||
|
Workspace_Organize_Read!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Sync!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_CreateDoc!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Users_Read!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Properties_Read!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Settings_Read!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Users_Manage!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Settings_Update!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Properties_Create!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Properties_Update!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Properties_Delete!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_Delete!: boolean;
|
||||||
|
@Field()
|
||||||
|
Workspace_TransferOwner!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class WorkspaceRolePermissions {
|
||||||
|
@Field(() => WorkspaceRole)
|
||||||
|
role!: WorkspaceRole;
|
||||||
|
|
||||||
|
@Field(() => WorkspacePermissions)
|
||||||
|
permissions!: WorkspacePermissions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workspace resolver
|
* Workspace resolver
|
||||||
* Public apis rate limit: 10 req/m
|
* Public apis rate limit: 10 req/m
|
||||||
@@ -75,8 +118,6 @@ class WorkspacePageMeta {
|
|||||||
*/
|
*/
|
||||||
@Resolver(() => WorkspaceType)
|
@Resolver(() => WorkspaceType)
|
||||||
export class WorkspaceResolver {
|
export class WorkspaceResolver {
|
||||||
private readonly logger = new Logger(WorkspaceResolver.name);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly cache: Cache,
|
private readonly cache: Cache,
|
||||||
private readonly prisma: PrismaClient,
|
private readonly prisma: PrismaClient,
|
||||||
@@ -86,29 +127,32 @@ export class WorkspaceResolver {
|
|||||||
private readonly event: EventBus,
|
private readonly event: EventBus,
|
||||||
private readonly mutex: RequestMutex,
|
private readonly mutex: RequestMutex,
|
||||||
private readonly workspaceService: WorkspaceService,
|
private readonly workspaceService: WorkspaceService,
|
||||||
private readonly workspaceStorage: PgWorkspaceDocStorageAdapter
|
private readonly workspaceStorage: PgWorkspaceDocStorageAdapter,
|
||||||
) {}
|
private readonly logger: AFFiNELogger
|
||||||
|
) {
|
||||||
|
logger.setContext(WorkspaceResolver.name);
|
||||||
|
}
|
||||||
|
|
||||||
@ResolveField(() => Permission, {
|
@ResolveField(() => WorkspaceRole, {
|
||||||
description: 'Permission of current signed in user in workspace',
|
description: 'Role of current signed in user in workspace',
|
||||||
complexity: 2,
|
complexity: 2,
|
||||||
})
|
})
|
||||||
async permission(
|
async role(
|
||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
@Parent() workspace: WorkspaceType
|
@Parent() workspace: WorkspaceType
|
||||||
) {
|
) {
|
||||||
// may applied in workspaces query
|
// may applied in workspaces query
|
||||||
if ('permission' in workspace) {
|
if ('role' in workspace) {
|
||||||
return workspace.permission;
|
return workspace.role;
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = await this.permissions.get(workspace.id, user.id);
|
const role = await this.permissions.get(workspace.id, user.id);
|
||||||
|
|
||||||
if (!permission) {
|
if (!role) {
|
||||||
throw new SpaceAccessDenied({ spaceId: workspace.id });
|
throw new SpaceAccessDenied({ spaceId: workspace.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
return permission;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField(() => Int, {
|
@ResolveField(() => Int, {
|
||||||
@@ -249,7 +293,7 @@ export class WorkspaceResolver {
|
|||||||
return this.permissions.tryCheckWorkspaceIs(
|
return this.permissions.tryCheckWorkspaceIs(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +323,7 @@ export class WorkspaceResolver {
|
|||||||
return {
|
return {
|
||||||
...workspace,
|
...workspace,
|
||||||
permission: type,
|
permission: type,
|
||||||
|
role: type,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -297,6 +342,25 @@ export class WorkspaceResolver {
|
|||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query(() => WorkspaceRolePermissions, {
|
||||||
|
description: 'Get workspace role permissions',
|
||||||
|
})
|
||||||
|
async workspaceRolePermissions(
|
||||||
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args('id') id: string
|
||||||
|
) {
|
||||||
|
const workspace = await this.prisma.workspaceUserPermission.findFirst({
|
||||||
|
where: { workspaceId: id, userId: user.id },
|
||||||
|
});
|
||||||
|
if (!workspace) {
|
||||||
|
throw new SpaceAccessDenied({ spaceId: id });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
role: workspace.type,
|
||||||
|
permissions: mapWorkspaceRoleToWorkspaceActions(workspace.type),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => WorkspaceType, {
|
@Mutation(() => WorkspaceType, {
|
||||||
description: 'Create a new workspace',
|
description: 'Create a new workspace',
|
||||||
})
|
})
|
||||||
@@ -312,7 +376,7 @@ export class WorkspaceResolver {
|
|||||||
public: false,
|
public: false,
|
||||||
permissions: {
|
permissions: {
|
||||||
create: {
|
create: {
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
status: WorkspaceMemberStatus.Accepted,
|
status: WorkspaceMemberStatus.Accepted,
|
||||||
@@ -323,21 +387,18 @@ export class WorkspaceResolver {
|
|||||||
|
|
||||||
if (init) {
|
if (init) {
|
||||||
// convert stream to buffer
|
// convert stream to buffer
|
||||||
const buffer = await new Promise<Buffer>(resolve => {
|
const chunks: Uint8Array[] = [];
|
||||||
const stream = init.createReadStream();
|
try {
|
||||||
const chunks: Uint8Array[] = [];
|
for await (const chunk of init.createReadStream()) {
|
||||||
stream.on('data', chunk => {
|
|
||||||
chunks.push(chunk);
|
chunks.push(chunk);
|
||||||
});
|
}
|
||||||
stream.on('error', () => {
|
} catch (e) {
|
||||||
resolve(Buffer.from([]));
|
this.logger.error('Failed to get file content from upload stream', e);
|
||||||
});
|
chunks.length = 0;
|
||||||
stream.on('end', () => {
|
}
|
||||||
resolve(Buffer.concat(chunks));
|
const buffer = chunks.length ? Buffer.concat(chunks) : null;
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (buffer.length) {
|
if (buffer) {
|
||||||
await this.prisma.snapshot.create({
|
await this.prisma.snapshot.create({
|
||||||
data: {
|
data: {
|
||||||
id: workspace.id,
|
id: workspace.id,
|
||||||
@@ -364,7 +425,7 @@ export class WorkspaceResolver {
|
|||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
id,
|
id,
|
||||||
user.id,
|
user.id,
|
||||||
isTeam ? Permission.Owner : Permission.Admin
|
isTeam ? WorkspaceRole.Owner : WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.prisma.workspace.update({
|
return this.prisma.workspace.update({
|
||||||
@@ -380,7 +441,7 @@ export class WorkspaceResolver {
|
|||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
@Args('id') id: string
|
@Args('id') id: string
|
||||||
) {
|
) {
|
||||||
await this.permissions.checkWorkspace(id, user.id, Permission.Owner);
|
await this.permissions.checkWorkspace(id, user.id, WorkspaceRole.Owner);
|
||||||
|
|
||||||
await this.prisma.workspace.delete({
|
await this.prisma.workspace.delete({
|
||||||
where: {
|
where: {
|
||||||
@@ -401,16 +462,16 @@ export class WorkspaceResolver {
|
|||||||
@Args('email') email: string,
|
@Args('email') email: string,
|
||||||
@Args('sendInviteMail', { nullable: true }) sendInviteMail: boolean,
|
@Args('sendInviteMail', { nullable: true }) sendInviteMail: boolean,
|
||||||
@Args('permission', {
|
@Args('permission', {
|
||||||
type: () => Permission,
|
type: () => WorkspaceRole,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
deprecationReason: 'never used',
|
deprecationReason: 'never used',
|
||||||
})
|
})
|
||||||
_permission?: Permission
|
_permission?: WorkspaceRole
|
||||||
) {
|
) {
|
||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -418,7 +479,7 @@ export class WorkspaceResolver {
|
|||||||
const lockFlag = `invite:${workspaceId}`;
|
const lockFlag = `invite:${workspaceId}`;
|
||||||
await using lock = await this.mutex.acquire(lockFlag);
|
await using lock = await this.mutex.acquire(lockFlag);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
return new TooManyRequest();
|
throw new TooManyRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
// member limit check
|
// member limit check
|
||||||
@@ -445,7 +506,7 @@ export class WorkspaceResolver {
|
|||||||
const inviteId = await this.permissions.grant(
|
const inviteId = await this.permissions.grant(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
target.id,
|
target.id,
|
||||||
Permission.Write
|
WorkspaceRole.Collaborator
|
||||||
);
|
);
|
||||||
if (sendInviteMail) {
|
if (sendInviteMail) {
|
||||||
try {
|
try {
|
||||||
@@ -474,10 +535,10 @@ export class WorkspaceResolver {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// pass through user friendly error
|
// pass through user friendly error
|
||||||
if (e instanceof UserFriendlyError) {
|
if (e instanceof UserFriendlyError) {
|
||||||
return e;
|
throw e;
|
||||||
}
|
}
|
||||||
this.logger.error('failed to invite user', e);
|
this.logger.error('failed to invite user', e);
|
||||||
return new TooManyRequest();
|
throw new TooManyRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,20 +573,20 @@ export class WorkspaceResolver {
|
|||||||
const isAdmin = await this.permissions.tryCheckWorkspaceIs(
|
const isAdmin = await this.permissions.tryCheckWorkspaceIs(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
userId,
|
userId,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
if (isTeam && isAdmin) {
|
if (isTeam && isAdmin) {
|
||||||
// only owner can revoke team workspace admin
|
// only owner can revoke team workspace admin
|
||||||
await this.permissions.checkWorkspaceIs(
|
await this.permissions.checkWorkspaceIs(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await this.permissions.checkWorkspace(
|
await this.permissions.checkWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Admin
|
WorkspaceRole.Admin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,7 +604,7 @@ export class WorkspaceResolver {
|
|||||||
const lockFlag = `invite:${workspaceId}`;
|
const lockFlag = `invite:${workspaceId}`;
|
||||||
await using lock = await this.mutex.acquire(lockFlag);
|
await using lock = await this.mutex.acquire(lockFlag);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
return new TooManyRequest();
|
throw new TooManyRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTeam = await this.quota.isTeamWorkspace(workspaceId);
|
const isTeam = await this.quota.isTeamWorkspace(workspaceId);
|
||||||
@@ -553,7 +614,7 @@ export class WorkspaceResolver {
|
|||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
if (status === WorkspaceMemberStatus.Accepted) {
|
if (status === WorkspaceMemberStatus.Accepted) {
|
||||||
return new AlreadyInSpace({ spaceId: workspaceId });
|
throw new AlreadyInSpace({ spaceId: workspaceId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// invite link
|
// invite link
|
||||||
@@ -568,7 +629,7 @@ export class WorkspaceResolver {
|
|||||||
await this.permissions.grant(
|
await this.permissions.grant(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Write,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
||||||
);
|
);
|
||||||
const memberCount =
|
const memberCount =
|
||||||
@@ -579,7 +640,7 @@ export class WorkspaceResolver {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} else if (!status) {
|
} else if (!status) {
|
||||||
return new MemberQuotaExceeded();
|
throw new MemberQuotaExceeded();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const inviteId = await this.permissions.grant(workspaceId, user.id);
|
const inviteId = await this.permissions.grant(workspaceId, user.id);
|
||||||
|
|||||||
@@ -8,17 +8,28 @@ import {
|
|||||||
PickType,
|
PickType,
|
||||||
registerEnumType,
|
registerEnumType,
|
||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
import { Workspace, WorkspaceMemberStatus } from '@prisma/client';
|
import { WorkspaceMemberStatus } from '@prisma/client';
|
||||||
import { SafeIntResolver } from 'graphql-scalars';
|
import { SafeIntResolver } from 'graphql-scalars';
|
||||||
|
|
||||||
import { Permission } from '../permission';
|
import { DocRole, WorkspaceRole } from '../permission';
|
||||||
import { UserType } from '../user/types';
|
import { UserType } from '../user/types';
|
||||||
|
|
||||||
registerEnumType(Permission, {
|
registerEnumType(WorkspaceRole, {
|
||||||
|
name: 'WorkspaceRole',
|
||||||
|
description: 'User role in workspace',
|
||||||
|
});
|
||||||
|
|
||||||
|
// @deprecated
|
||||||
|
registerEnumType(WorkspaceRole, {
|
||||||
name: 'Permission',
|
name: 'Permission',
|
||||||
description: 'User permission in workspace',
|
description: 'User permission in workspace',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerEnumType(DocRole, {
|
||||||
|
name: 'DocRole',
|
||||||
|
description: 'User permission in doc',
|
||||||
|
});
|
||||||
|
|
||||||
registerEnumType(WorkspaceMemberStatus, {
|
registerEnumType(WorkspaceMemberStatus, {
|
||||||
name: 'WorkspaceMemberStatus',
|
name: 'WorkspaceMemberStatus',
|
||||||
description: 'Member invite status in workspace',
|
description: 'Member invite status in workspace',
|
||||||
@@ -33,8 +44,14 @@ export class InviteUserType extends OmitType(
|
|||||||
@Field(() => ID)
|
@Field(() => ID)
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Field(() => Permission, { description: 'User permission in workspace' })
|
@Field(() => WorkspaceRole, {
|
||||||
permission!: Permission;
|
deprecationReason: 'Use role instead',
|
||||||
|
description: 'User permission in workspace',
|
||||||
|
})
|
||||||
|
permission!: WorkspaceRole;
|
||||||
|
|
||||||
|
@Field(() => WorkspaceRole, { description: 'User role in workspace' })
|
||||||
|
role!: WorkspaceRole;
|
||||||
|
|
||||||
@Field({ description: 'Invite id' })
|
@Field({ description: 'Invite id' })
|
||||||
inviteId!: string;
|
inviteId!: string;
|
||||||
@@ -52,22 +69,25 @@ export class InviteUserType extends OmitType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class WorkspaceType implements Partial<Workspace> {
|
export class WorkspaceFeatureType {
|
||||||
@Field(() => ID)
|
@Field(() => ID)
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Field({ description: 'is Public workspace' })
|
@Field({ description: 'is Public workspace' })
|
||||||
public!: boolean;
|
public!: boolean;
|
||||||
|
|
||||||
|
@Field({ description: 'Workspace created date' })
|
||||||
|
createdAt!: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class WorkspaceType extends WorkspaceFeatureType {
|
||||||
@Field({ description: 'Enable AI' })
|
@Field({ description: 'Enable AI' })
|
||||||
enableAi!: boolean;
|
enableAi!: boolean;
|
||||||
|
|
||||||
@Field({ description: 'Enable url previous when sharing' })
|
@Field({ description: 'Enable url previous when sharing' })
|
||||||
enableUrlPreview!: boolean;
|
enableUrlPreview!: boolean;
|
||||||
|
|
||||||
@Field({ description: 'Workspace created date' })
|
|
||||||
createdAt!: Date;
|
|
||||||
|
|
||||||
@Field(() => [InviteUserType], {
|
@Field(() => [InviteUserType], {
|
||||||
description: 'Members of workspace',
|
description: 'Members of workspace',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
export * from './feature';
|
export * from './feature';
|
||||||
export * from './page';
|
export * from './page';
|
||||||
export * from './permission';
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export enum Permission {
|
|
||||||
Read = 0,
|
|
||||||
Write = 1,
|
|
||||||
Admin = 10,
|
|
||||||
Owner = 99,
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
type WorkspacePageUserPermission as PageUserPermission,
|
type WorkspacePageUserPermission as PageUserPermission,
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
|
|
||||||
|
import { WorkspaceRole } from '../core/permission';
|
||||||
import { BaseModel } from './base';
|
import { BaseModel } from './base';
|
||||||
import { Permission, PublicPageMode } from './common';
|
import { PublicPageMode } from './common';
|
||||||
|
|
||||||
export type { Page };
|
export type { Page };
|
||||||
export type UpdatePageInput = {
|
export type UpdatePageInput = {
|
||||||
mode?: PublicPageMode;
|
mode?: PublicPageMode;
|
||||||
@@ -93,7 +93,7 @@ export class PageModel extends BaseModel {
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
pageId: string,
|
pageId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
): Promise<PageUserPermission> {
|
): Promise<PageUserPermission> {
|
||||||
let data = await this.db.workspacePageUserPermission.findUnique({
|
let data = await this.db.workspacePageUserPermission.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@@ -134,15 +134,15 @@ export class PageModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the new permission is owner, we need to revoke old owner
|
// If the new permission is owner, we need to revoke old owner
|
||||||
if (permission === Permission.Owner) {
|
if (permission === WorkspaceRole.Owner) {
|
||||||
await this.db.workspacePageUserPermission.updateMany({
|
await this.db.workspacePageUserPermission.updateMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pageId,
|
pageId,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
userId: { not: userId },
|
userId: { not: userId },
|
||||||
},
|
},
|
||||||
data: { type: Permission.Admin },
|
data: { type: WorkspaceRole.Admin },
|
||||||
});
|
});
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Change owner of workspace ${workspaceId} page ${pageId} to user ${userId}`
|
`Change owner of workspace ${workspaceId} page ${pageId} to user ${userId}`
|
||||||
@@ -163,7 +163,7 @@ export class PageModel extends BaseModel {
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
pageId: string,
|
pageId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
const count = await this.db.workspacePageUserPermission.count({
|
const count = await this.db.workspacePageUserPermission.count({
|
||||||
where: {
|
where: {
|
||||||
@@ -190,7 +190,7 @@ export class PageModel extends BaseModel {
|
|||||||
userId,
|
userId,
|
||||||
type: {
|
type: {
|
||||||
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading
|
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading
|
||||||
not: Permission.Owner,
|
not: WorkspaceRole.Owner,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
import { groupBy } from 'lodash-es';
|
import { groupBy } from 'lodash-es';
|
||||||
|
|
||||||
import { EventBus } from '../base';
|
import { EventBus } from '../base';
|
||||||
|
import { WorkspaceRole } from '../core/permission';
|
||||||
import { BaseModel } from './base';
|
import { BaseModel } from './base';
|
||||||
import { Permission } from './common';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Events {
|
interface Events {
|
||||||
@@ -90,7 +90,7 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
public: false,
|
public: false,
|
||||||
permissions: {
|
permissions: {
|
||||||
create: {
|
create: {
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
status: WorkspaceMemberStatus.Accepted,
|
status: WorkspaceMemberStatus.Accepted,
|
||||||
@@ -141,7 +141,7 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
const rows = await this.db.workspaceUserPermission.findMany({
|
const rows = await this.db.workspaceUserPermission.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
OR: this.acceptedCondition,
|
OR: this.acceptedCondition,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
@@ -177,7 +177,7 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
async grantMember(
|
async grantMember(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
permission: Permission = Permission.Read,
|
permission: WorkspaceRole = WorkspaceRole.Collaborator,
|
||||||
status: WorkspaceMemberStatus = WorkspaceMemberStatus.Pending
|
status: WorkspaceMemberStatus = WorkspaceMemberStatus.Pending
|
||||||
): Promise<WorkspaceUserPermission> {
|
): Promise<WorkspaceUserPermission> {
|
||||||
const data = await this.db.workspaceUserPermission.findUnique({
|
const data = await this.db.workspaceUserPermission.findUnique({
|
||||||
@@ -191,17 +191,19 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
// Create a new permission
|
// Create a new permission
|
||||||
// TODO(fengmk2): should we check the permission here? Like owner can't be pending?
|
|
||||||
const created = await this.db.workspaceUserPermission.create({
|
const created = await this.db.workspaceUserPermission.create({
|
||||||
data: {
|
data: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
userId,
|
userId,
|
||||||
type: permission,
|
type: permission,
|
||||||
status,
|
status:
|
||||||
|
permission === WorkspaceRole.Owner
|
||||||
|
? WorkspaceMemberStatus.Accepted
|
||||||
|
: status,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Granted workspace ${workspaceId} member ${userId} with permission ${permission}`
|
`Granted workspace ${workspaceId} member ${userId} with permission ${WorkspaceRole[permission]}`
|
||||||
);
|
);
|
||||||
await this.notifyMembersUpdated(workspaceId);
|
await this.notifyMembersUpdated(workspaceId);
|
||||||
return created;
|
return created;
|
||||||
@@ -216,14 +218,14 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
data: { type: permission },
|
data: { type: permission },
|
||||||
});
|
});
|
||||||
// If the new permission is owner, we need to revoke old owner
|
// If the new permission is owner, we need to revoke old owner
|
||||||
if (permission === Permission.Owner) {
|
if (permission === WorkspaceRole.Owner) {
|
||||||
await this.db.workspaceUserPermission.updateMany({
|
await this.db.workspaceUserPermission.updateMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
userId: { not: userId },
|
userId: { not: userId },
|
||||||
},
|
},
|
||||||
data: { type: Permission.Admin },
|
data: { type: WorkspaceRole.Admin },
|
||||||
});
|
});
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Change owner of workspace ${workspaceId} to ${userId}`
|
`Change owner of workspace ${workspaceId} to ${userId}`
|
||||||
@@ -318,7 +320,7 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
async isMember(
|
async isMember(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
permission: Permission = Permission.Read
|
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||||
) {
|
) {
|
||||||
const count = await this.db.workspaceUserPermission.count({
|
const count = await this.db.workspaceUserPermission.count({
|
||||||
where: {
|
where: {
|
||||||
@@ -340,7 +342,7 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
return await this.db.workspaceUserPermission.findFirst({
|
return await this.db.workspaceUserPermission.findFirst({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
type: Permission.Owner,
|
type: WorkspaceRole.Owner,
|
||||||
OR: this.acceptedCondition,
|
OR: this.acceptedCondition,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
@@ -356,7 +358,7 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
return await this.db.workspaceUserPermission.findMany({
|
return await this.db.workspaceUserPermission.findMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
type: Permission.Admin,
|
type: WorkspaceRole.Admin,
|
||||||
OR: this.acceptedCondition,
|
OR: this.acceptedCondition,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
@@ -394,7 +396,7 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
|
|
||||||
// We shouldn't revoke owner permission
|
// We shouldn't revoke owner permission
|
||||||
// should auto deleted by workspace/user delete cascading
|
// should auto deleted by workspace/user delete cascading
|
||||||
if (!member || member.type === Permission.Owner) {
|
if (!member || member.type === WorkspaceRole.Owner) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ export class CopilotResolver {
|
|||||||
await this.permissions.checkCloudPagePermission(
|
await this.permissions.checkCloudPagePermission(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
docId,
|
docId,
|
||||||
|
'Doc_Read',
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -368,6 +369,7 @@ export class CopilotResolver {
|
|||||||
await this.permissions.checkCloudPagePermission(
|
await this.permissions.checkCloudPagePermission(
|
||||||
options.workspaceId,
|
options.workspaceId,
|
||||||
options.docId,
|
options.docId,
|
||||||
|
'Doc_Read',
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
||||||
@@ -401,6 +403,7 @@ export class CopilotResolver {
|
|||||||
await this.permissions.checkCloudPagePermission(
|
await this.permissions.checkCloudPagePermission(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
docId,
|
docId,
|
||||||
|
'Doc_Update',
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${workspaceId}`;
|
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${workspaceId}`;
|
||||||
@@ -428,6 +431,7 @@ export class CopilotResolver {
|
|||||||
await this.permissions.checkCloudPagePermission(
|
await this.permissions.checkCloudPagePermission(
|
||||||
options.workspaceId,
|
options.workspaceId,
|
||||||
options.docId,
|
options.docId,
|
||||||
|
'Doc_Copy',
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;
|
||||||
@@ -456,6 +460,7 @@ export class CopilotResolver {
|
|||||||
await this.permissions.checkCloudPagePermission(
|
await this.permissions.checkCloudPagePermission(
|
||||||
options.workspaceId,
|
options.workspaceId,
|
||||||
options.docId,
|
options.docId,
|
||||||
|
'Doc_Delete',
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
if (!options.sessionIds.length) {
|
if (!options.sessionIds.length) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
|
|
||||||
import { ActionForbidden, Config } from '../../base';
|
import { ActionForbidden, Config } from '../../base';
|
||||||
import { CurrentUser } from '../../core/auth';
|
import { CurrentUser } from '../../core/auth';
|
||||||
import { Permission, PermissionService } from '../../core/permission';
|
import { PermissionService, WorkspaceRole } from '../../core/permission';
|
||||||
import { WorkspaceType } from '../../core/workspaces';
|
import { WorkspaceType } from '../../core/workspaces';
|
||||||
import { SubscriptionRecurring } from '../payment/types';
|
import { SubscriptionRecurring } from '../payment/types';
|
||||||
import { LicenseService } from './service';
|
import { LicenseService } from './service';
|
||||||
@@ -61,7 +61,7 @@ export class LicenseResolver {
|
|||||||
await this.permission.checkWorkspaceIs(
|
await this.permission.checkWorkspaceIs(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.service.getLicense(workspace.id);
|
return this.service.getLicense(workspace.id);
|
||||||
@@ -80,7 +80,7 @@ export class LicenseResolver {
|
|||||||
await this.permission.checkWorkspaceIs(
|
await this.permission.checkWorkspaceIs(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.service.activateTeamLicense(workspaceId, license);
|
return this.service.activateTeamLicense(workspaceId, license);
|
||||||
@@ -98,7 +98,7 @@ export class LicenseResolver {
|
|||||||
await this.permission.checkWorkspaceIs(
|
await this.permission.checkWorkspaceIs(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.service.deactivateTeamLicense(workspaceId);
|
return this.service.deactivateTeamLicense(workspaceId);
|
||||||
@@ -116,7 +116,7 @@ export class LicenseResolver {
|
|||||||
await this.permission.checkWorkspaceIs(
|
await this.permission.checkWorkspaceIs(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
user.id,
|
user.id,
|
||||||
Permission.Owner
|
WorkspaceRole.Owner
|
||||||
);
|
);
|
||||||
|
|
||||||
const { url } = await this.service.createCustomerPortal(workspaceId);
|
const { url } = await this.service.createCustomerPortal(workspaceId);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
WorkspaceIdRequiredToUpdateTeamSubscription,
|
WorkspaceIdRequiredToUpdateTeamSubscription,
|
||||||
} from '../../base';
|
} from '../../base';
|
||||||
import { CurrentUser, Public } from '../../core/auth';
|
import { CurrentUser, Public } from '../../core/auth';
|
||||||
import { Permission, PermissionService } from '../../core/permission';
|
import { PermissionService, WorkspaceRole } from '../../core/permission';
|
||||||
import { UserType } from '../../core/user';
|
import { UserType } from '../../core/user';
|
||||||
import { WorkspaceType } from '../../core/workspaces';
|
import { WorkspaceType } from '../../core/workspaces';
|
||||||
import { Invoice, Subscription, WorkspaceSubscriptionManager } from './manager';
|
import { Invoice, Subscription, WorkspaceSubscriptionManager } from './manager';
|
||||||
@@ -541,7 +541,11 @@ export class WorkspaceSubscriptionResolver {
|
|||||||
@CurrentUser() me: CurrentUser,
|
@CurrentUser() me: CurrentUser,
|
||||||
@Parent() workspace: WorkspaceType
|
@Parent() workspace: WorkspaceType
|
||||||
) {
|
) {
|
||||||
await this.permission.checkWorkspace(workspace.id, me.id, Permission.Owner);
|
await this.permission.checkWorkspace(
|
||||||
|
workspace.id,
|
||||||
|
me.id,
|
||||||
|
WorkspaceRole.Owner
|
||||||
|
);
|
||||||
return this.db.invoice.count({
|
return this.db.invoice.count({
|
||||||
where: {
|
where: {
|
||||||
targetId: workspace.id,
|
targetId: workspace.id,
|
||||||
@@ -557,7 +561,11 @@ export class WorkspaceSubscriptionResolver {
|
|||||||
take: number,
|
take: number,
|
||||||
@Args('skip', { type: () => Int, nullable: true }) skip?: number
|
@Args('skip', { type: () => Int, nullable: true }) skip?: number
|
||||||
) {
|
) {
|
||||||
await this.permission.checkWorkspace(workspace.id, me.id, Permission.Owner);
|
await this.permission.checkWorkspace(
|
||||||
|
workspace.id,
|
||||||
|
me.id,
|
||||||
|
WorkspaceRole.Owner
|
||||||
|
);
|
||||||
|
|
||||||
return this.db.invoice.findMany({
|
return this.db.invoice.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -204,12 +204,28 @@ type DocNotFoundDataType {
|
|||||||
spaceId: String!
|
spaceId: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""User permission in doc"""
|
||||||
|
enum DocRole {
|
||||||
|
Editor
|
||||||
|
External
|
||||||
|
Manager
|
||||||
|
Owner
|
||||||
|
Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocType {
|
||||||
|
id: String!
|
||||||
|
permissions: RolePermissions!
|
||||||
|
public: Boolean!
|
||||||
|
role: DocRole!
|
||||||
|
}
|
||||||
|
|
||||||
type EditorType {
|
type EditorType {
|
||||||
avatarUrl: String
|
avatarUrl: String
|
||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedSubscriptionPlanDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WrongSignInCredentialsDataType
|
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedSubscriptionPlanDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
|
||||||
|
|
||||||
enum ErrorNames {
|
enum ErrorNames {
|
||||||
ACCESS_DENIED
|
ACCESS_DENIED
|
||||||
@@ -240,8 +256,11 @@ enum ErrorNames {
|
|||||||
EMAIL_ALREADY_USED
|
EMAIL_ALREADY_USED
|
||||||
EMAIL_TOKEN_NOT_FOUND
|
EMAIL_TOKEN_NOT_FOUND
|
||||||
EMAIL_VERIFICATION_REQUIRED
|
EMAIL_VERIFICATION_REQUIRED
|
||||||
|
EXPECT_TO_GRANT_DOC_USER_ROLES
|
||||||
EXPECT_TO_PUBLISH_PAGE
|
EXPECT_TO_PUBLISH_PAGE
|
||||||
|
EXPECT_TO_REVOKE_DOC_USER_ROLES
|
||||||
EXPECT_TO_REVOKE_PUBLIC_PAGE
|
EXPECT_TO_REVOKE_PUBLIC_PAGE
|
||||||
|
EXPECT_TO_UPDATE_DOC_USER_ROLE
|
||||||
FAILED_TO_CHECKOUT
|
FAILED_TO_CHECKOUT
|
||||||
FAILED_TO_SAVE_UPDATES
|
FAILED_TO_SAVE_UPDATES
|
||||||
FAILED_TO_UPSERT_SNAPSHOT
|
FAILED_TO_UPSERT_SNAPSHOT
|
||||||
@@ -279,6 +298,7 @@ enum ErrorNames {
|
|||||||
SPACE_ACCESS_DENIED
|
SPACE_ACCESS_DENIED
|
||||||
SPACE_NOT_FOUND
|
SPACE_NOT_FOUND
|
||||||
SPACE_OWNER_NOT_FOUND
|
SPACE_OWNER_NOT_FOUND
|
||||||
|
SPACE_SHOULD_HAVE_ONLY_ONE_OWNER
|
||||||
SUBSCRIPTION_ALREADY_EXISTS
|
SUBSCRIPTION_ALREADY_EXISTS
|
||||||
SUBSCRIPTION_EXPIRED
|
SUBSCRIPTION_EXPIRED
|
||||||
SUBSCRIPTION_HAS_BEEN_CANCELED
|
SUBSCRIPTION_HAS_BEEN_CANCELED
|
||||||
@@ -296,10 +316,26 @@ enum ErrorNames {
|
|||||||
WORKSPACE_ID_REQUIRED_TO_UPDATE_TEAM_SUBSCRIPTION
|
WORKSPACE_ID_REQUIRED_TO_UPDATE_TEAM_SUBSCRIPTION
|
||||||
WORKSPACE_LICENSE_ALREADY_EXISTS
|
WORKSPACE_LICENSE_ALREADY_EXISTS
|
||||||
WORKSPACE_MEMBERS_EXCEED_LIMIT_TO_DOWNGRADE
|
WORKSPACE_MEMBERS_EXCEED_LIMIT_TO_DOWNGRADE
|
||||||
|
WORKSPACE_PERMISSION_NOT_FOUND
|
||||||
WRONG_SIGN_IN_CREDENTIALS
|
WRONG_SIGN_IN_CREDENTIALS
|
||||||
WRONG_SIGN_IN_METHOD
|
WRONG_SIGN_IN_METHOD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExpectToGrantDocUserRolesDataType {
|
||||||
|
docId: String!
|
||||||
|
spaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpectToRevokeDocUserRolesDataType {
|
||||||
|
docId: String!
|
||||||
|
spaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpectToUpdateDocUserRoleDataType {
|
||||||
|
docId: String!
|
||||||
|
spaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
"""The type of workspace feature"""
|
"""The type of workspace feature"""
|
||||||
enum FeatureType {
|
enum FeatureType {
|
||||||
AIEarlyAccess
|
AIEarlyAccess
|
||||||
@@ -321,6 +357,29 @@ input ForkChatSessionInput {
|
|||||||
workspaceId: String!
|
workspaceId: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input GrantDocUserRolesInput {
|
||||||
|
docId: String!
|
||||||
|
role: DocRole!
|
||||||
|
userIds: [String!]!
|
||||||
|
workspaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrantedDocUserEdge {
|
||||||
|
cursor: String!
|
||||||
|
user: GrantedDocUserType!
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrantedDocUserType {
|
||||||
|
role: DocRole!
|
||||||
|
user: UserType!
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrantedDocUsersConnection {
|
||||||
|
edges: [GrantedDocUserEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
totalCount: Int!
|
||||||
|
}
|
||||||
|
|
||||||
type HumanReadableQuotaType {
|
type HumanReadableQuotaType {
|
||||||
blobLimit: String!
|
blobLimit: String!
|
||||||
copilotActionLimit: String
|
copilotActionLimit: String
|
||||||
@@ -418,7 +477,10 @@ type InviteUserType {
|
|||||||
name: String
|
name: String
|
||||||
|
|
||||||
"""User permission in workspace"""
|
"""User permission in workspace"""
|
||||||
permission: Permission!
|
permission: Permission! @deprecated(reason: "Use role instead")
|
||||||
|
|
||||||
|
"""User role in workspace"""
|
||||||
|
role: Permission!
|
||||||
|
|
||||||
"""Member invite status in workspace"""
|
"""Member invite status in workspace"""
|
||||||
status: WorkspaceMemberStatus!
|
status: WorkspaceMemberStatus!
|
||||||
@@ -548,6 +610,7 @@ type Mutation {
|
|||||||
"""Create a chat session"""
|
"""Create a chat session"""
|
||||||
forkCopilotSession(options: ForkChatSessionInput!): String!
|
forkCopilotSession(options: ForkChatSessionInput!): String!
|
||||||
generateLicenseKey(sessionId: String!): String!
|
generateLicenseKey(sessionId: String!): String!
|
||||||
|
grantDocUserRoles(input: GrantDocUserRolesInput!): Boolean!
|
||||||
grantMember(permission: Permission!, userId: String!, workspaceId: String!): String!
|
grantMember(permission: Permission!, userId: String!, workspaceId: String!): String!
|
||||||
invite(email: String!, permission: Permission @deprecated(reason: "never used"), sendInviteMail: Boolean, workspaceId: String!): String!
|
invite(email: String!, permission: Permission @deprecated(reason: "never used"), sendInviteMail: Boolean, workspaceId: String!): String!
|
||||||
inviteBatch(emails: [String!]!, sendInviteMail: Boolean, workspaceId: String!): [InviteResult!]!
|
inviteBatch(emails: [String!]!, sendInviteMail: Boolean, workspaceId: String!): [InviteResult!]!
|
||||||
@@ -561,6 +624,7 @@ type Mutation {
|
|||||||
removeWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
removeWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
||||||
resumeSubscription(idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`"), plan: SubscriptionPlan = Pro, workspaceId: String): SubscriptionType!
|
resumeSubscription(idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`"), plan: SubscriptionPlan = Pro, workspaceId: String): SubscriptionType!
|
||||||
revoke(userId: String!, workspaceId: String!): Boolean!
|
revoke(userId: String!, workspaceId: String!): Boolean!
|
||||||
|
revokeDocUserRoles(docId: String!, userIds: [String!]!): Boolean!
|
||||||
revokeInviteLink(workspaceId: String!): Boolean!
|
revokeInviteLink(workspaceId: String!): Boolean!
|
||||||
revokePage(pageId: String!, workspaceId: String!): Boolean! @deprecated(reason: "use revokePublicPage")
|
revokePage(pageId: String!, workspaceId: String!): Boolean! @deprecated(reason: "use revokePublicPage")
|
||||||
revokePublicPage(pageId: String!, workspaceId: String!): WorkspacePage!
|
revokePublicPage(pageId: String!, workspaceId: String!): WorkspacePage!
|
||||||
@@ -578,6 +642,7 @@ type Mutation {
|
|||||||
|
|
||||||
"""Update a chat session"""
|
"""Update a chat session"""
|
||||||
updateCopilotSession(options: UpdateChatSessionInput!): String!
|
updateCopilotSession(options: UpdateChatSessionInput!): String!
|
||||||
|
updateDocUserRole(docId: String!, role: DocRole!, userId: String!): Boolean!
|
||||||
updateProfile(input: UpdateUserInput!): UserType!
|
updateProfile(input: UpdateUserInput!): UserType!
|
||||||
|
|
||||||
"""update server runtime configurable setting"""
|
"""update server runtime configurable setting"""
|
||||||
@@ -611,6 +676,23 @@ enum OAuthProviderType {
|
|||||||
OIDC
|
OIDC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input PageGrantedUsersInput {
|
||||||
|
"""Cursor"""
|
||||||
|
after: String
|
||||||
|
|
||||||
|
"""Cursor"""
|
||||||
|
before: String
|
||||||
|
first: Int!
|
||||||
|
offset: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageInfo {
|
||||||
|
endCursor: String
|
||||||
|
hasNextPage: Boolean!
|
||||||
|
hasPreviousPage: Boolean!
|
||||||
|
startCursor: String
|
||||||
|
}
|
||||||
|
|
||||||
type PasswordLimitsType {
|
type PasswordLimitsType {
|
||||||
maxLength: Int!
|
maxLength: Int!
|
||||||
minLength: Int!
|
minLength: Int!
|
||||||
@@ -619,9 +701,9 @@ type PasswordLimitsType {
|
|||||||
"""User permission in workspace"""
|
"""User permission in workspace"""
|
||||||
enum Permission {
|
enum Permission {
|
||||||
Admin
|
Admin
|
||||||
|
Collaborator
|
||||||
|
External
|
||||||
Owner
|
Owner
|
||||||
Read
|
|
||||||
Write
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""The mode which the public page default in"""
|
"""The mode which the public page default in"""
|
||||||
@@ -651,7 +733,7 @@ type Query {
|
|||||||
|
|
||||||
"""List all copilot prompts"""
|
"""List all copilot prompts"""
|
||||||
listCopilotPrompts: [CopilotPromptType!]!
|
listCopilotPrompts: [CopilotPromptType!]!
|
||||||
listWorkspaceFeatures(feature: FeatureType!): [WorkspaceType!]!
|
listWorkspaceFeatures(feature: FeatureType!): [WorkspaceFeatureType!]!
|
||||||
prices: [SubscriptionPrice!]!
|
prices: [SubscriptionPrice!]!
|
||||||
|
|
||||||
"""server config"""
|
"""server config"""
|
||||||
@@ -679,6 +761,9 @@ type Query {
|
|||||||
"""Get workspace by id"""
|
"""Get workspace by id"""
|
||||||
workspace(id: String!): WorkspaceType!
|
workspace(id: String!): WorkspaceType!
|
||||||
|
|
||||||
|
"""Get workspace role permissions"""
|
||||||
|
workspaceRolePermissions(id: String!): WorkspaceRolePermissions!
|
||||||
|
|
||||||
"""Get all accessible workspaces for current user"""
|
"""Get all accessible workspaces for current user"""
|
||||||
workspaces: [WorkspaceType!]!
|
workspaces: [WorkspaceType!]!
|
||||||
}
|
}
|
||||||
@@ -713,6 +798,35 @@ type RemoveAvatar {
|
|||||||
success: Boolean!
|
success: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RolePermissions {
|
||||||
|
Doc_Copy: Boolean!
|
||||||
|
Doc_Delete: Boolean!
|
||||||
|
Doc_Duplicate: Boolean!
|
||||||
|
Doc_Properties_Read: Boolean!
|
||||||
|
Doc_Properties_Update: Boolean!
|
||||||
|
Doc_Publish: Boolean!
|
||||||
|
Doc_Read: Boolean!
|
||||||
|
Doc_Restore: Boolean!
|
||||||
|
Doc_TransferOwner: Boolean!
|
||||||
|
Doc_Trash: Boolean!
|
||||||
|
Doc_Update: Boolean!
|
||||||
|
Doc_Users_Manage: Boolean!
|
||||||
|
Doc_Users_Read: Boolean!
|
||||||
|
Workspace_CreateDoc: Boolean!
|
||||||
|
Workspace_Delete: Boolean!
|
||||||
|
Workspace_Organize_Read: Boolean!
|
||||||
|
Workspace_Properties_Create: Boolean!
|
||||||
|
Workspace_Properties_Delete: Boolean!
|
||||||
|
Workspace_Properties_Read: Boolean!
|
||||||
|
Workspace_Properties_Update: Boolean!
|
||||||
|
Workspace_Settings_Read: Boolean!
|
||||||
|
Workspace_Settings_Update: Boolean!
|
||||||
|
Workspace_Sync: Boolean!
|
||||||
|
Workspace_TransferOwner: Boolean!
|
||||||
|
Workspace_Users_Manage: Boolean!
|
||||||
|
Workspace_Users_Read: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type RuntimeConfigNotFoundDataType {
|
type RuntimeConfigNotFoundDataType {
|
||||||
key: String!
|
key: String!
|
||||||
}
|
}
|
||||||
@@ -814,6 +928,10 @@ type SpaceOwnerNotFoundDataType {
|
|||||||
spaceId: String!
|
spaceId: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SpaceShouldHaveOnlyOneOwnerDataType {
|
||||||
|
spaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
type SubscriptionAlreadyExistsDataType {
|
type SubscriptionAlreadyExistsDataType {
|
||||||
plan: String!
|
plan: String!
|
||||||
}
|
}
|
||||||
@@ -988,6 +1106,15 @@ type WorkspaceBlobSizes {
|
|||||||
size: SafeInt!
|
size: SafeInt!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WorkspaceFeatureType {
|
||||||
|
"""Workspace created date"""
|
||||||
|
createdAt: DateTime!
|
||||||
|
id: ID!
|
||||||
|
|
||||||
|
"""is Public workspace"""
|
||||||
|
public: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
"""Workspace invite link expire time"""
|
"""Workspace invite link expire time"""
|
||||||
enum WorkspaceInviteLinkExpireTime {
|
enum WorkspaceInviteLinkExpireTime {
|
||||||
OneDay
|
OneDay
|
||||||
@@ -1023,6 +1150,31 @@ type WorkspacePageMeta {
|
|||||||
updatedBy: EditorType
|
updatedBy: EditorType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WorkspacePermissionNotFoundDataType {
|
||||||
|
spaceId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkspacePermissions {
|
||||||
|
Workspace_CreateDoc: Boolean!
|
||||||
|
Workspace_Delete: Boolean!
|
||||||
|
Workspace_Organize_Read: Boolean!
|
||||||
|
Workspace_Properties_Create: Boolean!
|
||||||
|
Workspace_Properties_Delete: Boolean!
|
||||||
|
Workspace_Properties_Read: Boolean!
|
||||||
|
Workspace_Properties_Update: Boolean!
|
||||||
|
Workspace_Settings_Read: Boolean!
|
||||||
|
Workspace_Settings_Update: Boolean!
|
||||||
|
Workspace_Sync: Boolean!
|
||||||
|
Workspace_TransferOwner: Boolean!
|
||||||
|
Workspace_Users_Manage: Boolean!
|
||||||
|
Workspace_Users_Read: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkspaceRolePermissions {
|
||||||
|
permissions: WorkspacePermissions!
|
||||||
|
role: Permission!
|
||||||
|
}
|
||||||
|
|
||||||
type WorkspaceType {
|
type WorkspaceType {
|
||||||
"""Available features of workspace"""
|
"""Available features of workspace"""
|
||||||
availableFeatures: [FeatureType!]!
|
availableFeatures: [FeatureType!]!
|
||||||
@@ -1069,11 +1221,14 @@ type WorkspaceType {
|
|||||||
"""Owner of workspace"""
|
"""Owner of workspace"""
|
||||||
owner: UserType!
|
owner: UserType!
|
||||||
|
|
||||||
|
"""Page granted users list"""
|
||||||
|
pageGrantedUsersList(pageGrantedUsersInput: PageGrantedUsersInput!, pageId: String!): GrantedDocUsersConnection!
|
||||||
|
|
||||||
"""Cloud page metadata of workspace"""
|
"""Cloud page metadata of workspace"""
|
||||||
pageMeta(pageId: String!): WorkspacePageMeta!
|
pageMeta(pageId: String!): WorkspacePageMeta!
|
||||||
|
|
||||||
"""Permission of current signed in user in workspace"""
|
"""Check if current user has permission to access the page"""
|
||||||
permission: Permission!
|
pagePermission(pageId: String!): DocType!
|
||||||
|
|
||||||
"""is Public workspace"""
|
"""is Public workspace"""
|
||||||
public: Boolean!
|
public: Boolean!
|
||||||
@@ -1087,6 +1242,9 @@ type WorkspaceType {
|
|||||||
"""quota of workspace"""
|
"""quota of workspace"""
|
||||||
quota: QuotaQueryType!
|
quota: QuotaQueryType!
|
||||||
|
|
||||||
|
"""Role of current signed in user in workspace"""
|
||||||
|
role: Permission!
|
||||||
|
|
||||||
"""Shared pages of workspace"""
|
"""Shared pages of workspace"""
|
||||||
sharedPages: [String!]! @deprecated(reason: "use WorkspaceType.publicPages")
|
sharedPages: [String!]! @deprecated(reason: "use WorkspaceType.publicPages")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user