From e2a6d828a9b2d5a36b53f7791b1fc21f7fb187e3 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 30 Sep 2023 12:38:16 -0400 Subject: [PATCH] Allow creating published share record, closes #5902 --- server/routes/api/shares/schema.ts | 8 ++ server/routes/api/shares/shares.test.ts | 61 +++++++++++- server/routes/api/shares/shares.ts | 121 +++++++++++++----------- 3 files changed, 132 insertions(+), 58 deletions(-) diff --git a/server/routes/api/shares/schema.ts b/server/routes/api/shares/schema.ts index 6b4076176..f5db3a652 100644 --- a/server/routes/api/shares/schema.ts +++ b/server/routes/api/shares/schema.ts @@ -68,6 +68,14 @@ export const SharesCreateSchema = BaseSchema.extend({ .refine((val) => isUUID(val) || SLUG_URL_REGEX.test(val), { message: "must be uuid or url slug", }), + published: z.boolean().default(false), + urlId: z + .string() + .regex(SHARE_URL_SLUG_REGEX, { + message: "must contain only alphanumeric and dashes", + }) + .optional(), + includeChildDocuments: z.boolean().default(false), }), }); diff --git a/server/routes/api/shares/shares.test.ts b/server/routes/api/shares/shares.test.ts index 670e25355..121960097 100644 --- a/server/routes/api/shares/shares.test.ts +++ b/server/routes/api/shares/shares.test.ts @@ -226,7 +226,66 @@ describe("#shares.create", () => { expect(body.data.documentTitle).toBe(document.title); }); - it("should allow creating a share record with read-only permissions but no publishing", async () => { + it("should allow creating a published share record for document", async () => { + const user = await buildUser(); + const document = await buildDocument({ + userId: user.id, + teamId: user.teamId, + }); + const res = await server.post("/api/shares.create", { + body: { + token: user.getJwtToken(), + documentId: document.id, + includeChildDocuments: true, + published: true, + urlId: "test", + }, + }); + const body = await res.json(); + expect(res.status).toEqual(200); + expect(body.data.published).toBe(true); + expect(body.data.includeChildDocuments).toBe(true); + expect(body.data.urlId).toBe("test"); + expect(body.data.documentTitle).toBe(document.title); + }); + + it("should fail creating a share record with read-only permissions and publishing", async () => { + const team = await buildTeam(); + const user = await buildUser({ teamId: team.id }); + const collection = await buildCollection({ + userId: user.id, + teamId: team.id, + }); + const document = await buildDocument({ + userId: user.id, + collectionId: collection.id, + teamId: team.id, + }); + collection.permission = null; + await collection.save(); + await UserPermission.update( + { + userId: user.id, + permission: CollectionPermission.Read, + }, + { + where: { + createdById: user.id, + collectionId: collection.id, + }, + } + ); + const res = await server.post("/api/shares.create", { + body: { + token: user.getJwtToken(), + documentId: document.id, + published: true, + }, + }); + expect(res.status).toEqual(403); + }); + + it("should allow creating a share record with read-only permissions but not publishing", async () => { const team = await buildTeam(); const user = await buildUser({ teamId: team.id }); const collection = await buildCollection({ diff --git a/server/routes/api/shares/shares.ts b/server/routes/api/shares/shares.ts index f2c883a7c..cc4e61af5 100644 --- a/server/routes/api/shares/shares.ts +++ b/server/routes/api/shares/shares.ts @@ -161,6 +161,69 @@ router.post( } ); +router.post( + "shares.create", + auth(), + validate(T.SharesCreateSchema), + async (ctx: APIContext) => { + const { documentId, published, urlId, includeChildDocuments } = + ctx.input.body; + const { user } = ctx.state.auth; + const document = await Document.findByPk(documentId, { + userId: user.id, + }); + + // user could be creating the share link to share with team members + authorize(user, "read", document); + + if (published) { + authorize(user, "share", user.team); + authorize(user, "share", document); + } + + const [share, isCreated] = await Share.findOrCreate({ + where: { + documentId, + teamId: user.teamId, + revokedAt: null, + }, + defaults: { + userId: user.id, + published, + includeChildDocuments, + urlId, + }, + }); + + if (isCreated) { + await Event.create({ + name: "shares.create", + documentId, + collectionId: document.collectionId, + modelId: share.id, + teamId: user.teamId, + actorId: user.id, + data: { + name: document.title, + published, + includeChildDocuments, + urlId, + }, + ip: ctx.request.ip, + }); + } + + share.team = user.team; + share.user = user; + share.document = document; + + ctx.body = { + data: presentShare(share), + policies: presentPolicies(user, [share]), + }; + } +); + router.post( "shares.update", auth(), @@ -169,8 +232,7 @@ router.post( const { id, includeChildDocuments, published, urlId } = ctx.input.body; const { user } = ctx.state.auth; - const team = await Team.findByPk(user.teamId); - authorize(user, "share", team); + authorize(user, "share", user.team); // fetch the share with document and collection. const share = await Share.scope({ @@ -218,61 +280,6 @@ router.post( } ); -router.post( - "shares.create", - auth(), - validate(T.SharesCreateSchema), - async (ctx: APIContext) => { - const { documentId } = ctx.input.body; - const { user } = ctx.state.auth; - const document = await Document.findByPk(documentId, { - userId: user.id, - }); - - // user could be creating the share link to share with team members - authorize(user, "read", document); - - const team = await Team.findByPk(user.teamId); - - const [share, isCreated] = await Share.findOrCreate({ - where: { - documentId, - teamId: user.teamId, - revokedAt: null, - }, - defaults: { - userId: user.id, - }, - }); - - if (isCreated) { - await Event.create({ - name: "shares.create", - documentId, - collectionId: document.collectionId, - modelId: share.id, - teamId: user.teamId, - actorId: user.id, - data: { - name: document.title, - }, - ip: ctx.request.ip, - }); - } - - if (team) { - share.team = team; - } - share.user = user; - share.document = document; - - ctx.body = { - data: presentShare(share), - policies: presentPolicies(user, [share]), - }; - } -); - router.post( "shares.revoke", auth(),