refactor(server): separate page visibility from workspace permission (#4836)

This commit is contained in:
liuyi
2023-11-06 11:49:39 +08:00
committed by GitHub
parent e8987457ab
commit f491ff94cc
19 changed files with 796 additions and 362 deletions
+2 -2
View File
@@ -60,7 +60,7 @@ const FakePrisma = {
return { id: this.id, blob: Buffer.from([0, 0]) };
},
},
get userWorkspacePermission() {
get workspaceUserPermission() {
return {
id: randomUUID(),
prisma: this,
@@ -79,7 +79,7 @@ const FakePrisma = {
async findFirstOrThrow() {
return { id: this.id, user: this.prisma.fakeUser };
},
async userWorkspacePermission() {
async workspaceUserPermission() {
return {
id: randomUUID(),
createdAt: new Date(),
+24 -35
View File
@@ -78,11 +78,11 @@ async function createWorkspace(
return res.body.data.createWorkspace;
}
export async function getWorkspaceSharedPages(
export async function getWorkspacePublicPages(
app: INestApplication,
token: string,
workspaceId: string
): Promise<string[]> {
) {
const res = await request(app.getHttpServer())
.post(gql)
.auth(token, { type: 'bearer' })
@@ -91,13 +91,16 @@ export async function getWorkspaceSharedPages(
query: `
query {
workspace(id: "${workspaceId}") {
sharedPages
publicPages {
id
mode
}
}
}
`,
})
.expect(200);
return res.body.data.workspace.sharedPages;
return res.body.data.workspace.publicPages;
}
async function getWorkspace(
@@ -210,26 +213,6 @@ async function acceptInviteById(
return res.body.data.acceptInviteById;
}
async function acceptInvite(
app: INestApplication,
token: string,
workspaceId: string
): Promise<boolean> {
const res = await request(app.getHttpServer())
.post(gql)
.auth(token, { type: 'bearer' })
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
.send({
query: `
mutation {
acceptInvite(workspaceId: "${workspaceId}")
}
`,
})
.expect(200);
return res.body.data.acceptInvite;
}
async function leaveWorkspace(
app: INestApplication,
token: string,
@@ -272,12 +255,12 @@ async function revokeUser(
return res.body.data.revoke;
}
async function sharePage(
async function publishPage(
app: INestApplication,
token: string,
workspaceId: string,
pageId: string
): Promise<boolean | string> {
) {
const res = await request(app.getHttpServer())
.post(gql)
.auth(token, { type: 'bearer' })
@@ -285,20 +268,23 @@ async function sharePage(
.send({
query: `
mutation {
sharePage(workspaceId: "${workspaceId}", pageId: "${pageId}")
publishPage(workspaceId: "${workspaceId}", pageId: "${pageId}") {
id
mode
}
}
`,
})
.expect(200);
return res.body.errors?.[0]?.message || res.body.data?.sharePage;
return res.body.errors?.[0]?.message || res.body.data?.publishPage;
}
async function revokePage(
async function revokePublicPage(
app: INestApplication,
token: string,
workspaceId: string,
pageId: string
): Promise<boolean | string> {
) {
const res = await request(app.getHttpServer())
.post(gql)
.auth(token, { type: 'bearer' })
@@ -306,12 +292,16 @@ async function revokePage(
.send({
query: `
mutation {
revokePage(workspaceId: "${workspaceId}", pageId: "${pageId}")
revokePublicPage(workspaceId: "${workspaceId}", pageId: "${pageId}") {
id
mode
public
}
}
`,
})
.expect(200);
return res.body.errors?.[0]?.message || res.body.data?.revokePage;
return res.body.errors?.[0]?.message || res.body.data?.revokePublicPage;
}
async function listBlobs(
@@ -572,7 +562,6 @@ export class FakePrisma {
}
export {
acceptInvite,
acceptInviteById,
changeEmail,
checkBlobSize,
@@ -587,12 +576,12 @@ export {
inviteUser,
leaveWorkspace,
listBlobs,
revokePage,
publishPage,
revokePublicPage,
revokeUser,
sendChangeEmail,
sendVerifyChangeEmail,
setBlob,
sharePage,
signUp,
updateWorkspace,
};
@@ -12,7 +12,6 @@ import { AppModule } from '../src/app';
import { MailService } from '../src/modules/auth/mailer';
import { AuthService } from '../src/modules/auth/service';
import {
acceptInvite,
acceptInviteById,
createWorkspace,
getWorkspace,
@@ -78,33 +77,20 @@ test('should invite a user', async t => {
t.truthy(invite, 'failed to invite user');
});
test('should accept an invite', async t => {
const { app } = t.context;
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
const workspace = await createWorkspace(app, u1.token.token);
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
const accept = await acceptInvite(app, u2.token.token, workspace.id);
t.is(accept, true, 'failed to accept invite');
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
const currMember = currWorkspace.members.find(u => u.email === u2.email);
t.not(currMember, undefined, 'failed to invite user');
t.is(currMember!.id, u2.id, 'failed to invite user');
t.true(!currMember!.accepted, 'failed to invite user');
t.pass();
});
test('should leave a workspace', async t => {
const { app } = t.context;
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
const workspace = await createWorkspace(app, u1.token.token);
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
await acceptInvite(app, u2.token.token, workspace.id);
const id = await inviteUser(
app,
u1.token.token,
workspace.id,
u2.email,
'Admin'
);
await acceptInviteById(app, workspace.id, id, false);
const leave = await leaveWorkspace(app, u2.token.token, workspace.id);
@@ -253,11 +239,23 @@ test('should support pagination for member', async t => {
const u3 = await signUp(app, 'u3', 'u3@affine.pro', '1');
const workspace = await createWorkspace(app, u1.token.token);
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
await inviteUser(app, u1.token.token, workspace.id, u3.email, 'Admin');
const invite1 = await inviteUser(
app,
u1.token.token,
workspace.id,
u2.email,
'Admin'
);
const invite2 = await inviteUser(
app,
u1.token.token,
workspace.id,
u3.email,
'Admin'
);
await acceptInvite(app, u2.token.token, workspace.id);
await acceptInvite(app, u3.token.token, workspace.id);
await acceptInviteById(app, workspace.id, invite1, false);
await acceptInviteById(app, workspace.id, invite2, false);
const firstPageWorkspace = await getWorkspace(
app,
@@ -12,7 +12,7 @@ import { StorageProvide } from '../src/storage';
import { FakePrisma } from './utils';
class FakePermission {
async tryCheck() {
async tryCheckWorkspace() {
return true;
}
async getWorkspaceOwner() {
@@ -37,7 +37,7 @@ test.beforeEach(async t => {
})
.overrideProvider(PrismaService)
.useValue({
userWorkspacePermission: {
workspaceUserPermission: {
async findMany() {
return [];
},
+57 -26
View File
@@ -7,14 +7,14 @@ import request from 'supertest';
import { AppModule } from '../src/app';
import {
acceptInvite,
acceptInviteById,
createWorkspace,
currentUser,
getPublicWorkspace,
getWorkspaceSharedPages,
getWorkspacePublicPages,
inviteUser,
revokePage,
sharePage,
publishPage,
revokePublicPage,
signUp,
updateWorkspace,
} from './utils';
@@ -122,15 +122,19 @@ test('should share a page', async t => {
const workspace = await createWorkspace(app, u1.token.token);
const share = await sharePage(app, u1.token.token, workspace.id, 'page1');
t.true(share, 'failed to share page');
const pages = await getWorkspaceSharedPages(
const share = await publishPage(app, u1.token.token, workspace.id, 'page1');
t.is(share.id, 'page1', 'failed to share page');
const pages = await getWorkspacePublicPages(
app,
u1.token.token,
workspace.id
);
t.is(pages.length, 1, 'failed to get shared pages');
t.is(pages[0], 'page1', 'failed to get shared page: page1');
t.deepEqual(
pages[0],
{ id: 'page1', mode: 'Page' },
'failed to get shared page: page1'
);
const resp1 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
@@ -139,7 +143,7 @@ test('should share a page', async t => {
const resp2 = await request(app.getHttpServer()).get(
`/api/workspaces/${workspace.id}/docs/${workspace.id}`
);
t.is(resp2.statusCode, 200, 'should not get root doc without token');
t.is(resp2.statusCode, 200, 'failed to get root doc with public pages');
const resp3 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/page1`)
@@ -152,32 +156,55 @@ test('should share a page', async t => {
// 404 because we don't put the page doc to server
t.is(resp4.statusCode, 404, 'should not get shared doc without token');
const msg1 = await sharePage(app, u2.token.token, 'not_exists_ws', 'page2');
const msg1 = await publishPage(app, u2.token.token, 'not_exists_ws', 'page2');
t.is(msg1, 'Permission denied', 'unauthorized user can share page');
const msg2 = await revokePage(app, u2.token.token, 'not_exists_ws', 'page2');
const msg2 = await revokePublicPage(
app,
u2.token.token,
'not_exists_ws',
'page2'
);
t.is(msg2, 'Permission denied', 'unauthorized user can share page');
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
await acceptInvite(app, u2.token.token, workspace.id);
const invited = await sharePage(app, u2.token.token, workspace.id, 'page2');
t.true(invited, 'failed to share page');
await acceptInviteById(
app,
workspace.id,
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin')
);
const invited = await publishPage(app, u2.token.token, workspace.id, 'page2');
t.is(invited.id, 'page2', 'failed to share page');
const revoke = await revokePage(app, u1.token.token, workspace.id, 'page1');
t.true(revoke, 'failed to revoke page');
const pages2 = await getWorkspaceSharedPages(
const revoke = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page1'
);
t.false(revoke.public, 'failed to revoke page');
const pages2 = await getWorkspacePublicPages(
app,
u1.token.token,
workspace.id
);
t.is(pages2.length, 1, 'failed to get shared pages');
t.is(pages2[0], 'page2', 'failed to get shared page: page2');
t.is(pages2[0].id, 'page2', 'failed to get shared page: page2');
const msg3 = await revokePage(app, u1.token.token, workspace.id, 'page3');
t.false(msg3, 'can revoke non-exists page');
const msg3 = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page3'
);
t.is(msg3, 'Page is not public');
const msg4 = await revokePage(app, u1.token.token, workspace.id, 'page2');
t.true(msg4, 'failed to revoke page');
const page3 = await getWorkspaceSharedPages(
const msg4 = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page2'
);
t.false(msg4.public, 'failed to revoke page');
const page3 = await getWorkspacePublicPages(
app,
u1.token.token,
workspace.id
@@ -211,13 +238,17 @@ test('should can get workspace doc', async t => {
.auth(u2.token.token, { type: 'bearer' })
.expect(403);
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.auth(u2.token.token, { type: 'bearer' })
.expect(403);
await acceptInvite(app, u2.token.token, workspace.id);
await acceptInviteById(
app,
workspace.id,
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin')
);
const res2 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.auth(u2.token.token, { type: 'bearer' })