From 7efbf70f45dbe660f44d3fe33d95bfa8e9451e5e Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 21 Jul 2026 14:00:31 +0000 Subject: [PATCH] fix(server): make concurrent unstars idempotent Co-Authored-By: Paperclip --- .../resource-memberships-routes.test.ts | 17 +++++++++-------- server/src/services/resource-memberships.ts | 10 +++++++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/server/src/__tests__/resource-memberships-routes.test.ts b/server/src/__tests__/resource-memberships-routes.test.ts index 92e45572d17..bd57ec2d81a 100644 --- a/server/src/__tests__/resource-memberships-routes.test.ts +++ b/server/src/__tests__/resource-memberships-routes.test.ts @@ -447,14 +447,15 @@ describeEmbeddedPostgres("resource membership routes", () => { expect(list.body.documentStarredAt[documentId]).toBe(first.body.starredAt); await expect(db.select().from(documentMemberships)).resolves.toHaveLength(1); - await request(app) - .put(`/api/companies/${companyId}/resource-memberships/me/documents/${documentId}`) - .send({ starred: false }) - .expect(200); - await request(app) - .put(`/api/companies/${companyId}/resource-memberships/me/documents/${documentId}`) - .send({ starred: false }) - .expect(200); + const unstars = await Promise.all([ + request(app) + .put(`/api/companies/${companyId}/resource-memberships/me/documents/${documentId}`) + .send({ starred: false }), + request(app) + .put(`/api/companies/${companyId}/resource-memberships/me/documents/${documentId}`) + .send({ starred: false }), + ]); + expect(unstars.map((response) => response.status)).toEqual([200, 200]); await expect(db.select().from(documentMemberships)).resolves.toHaveLength(0); await request(app) diff --git a/server/src/services/resource-memberships.ts b/server/src/services/resource-memberships.ts index 276e49d2389..54d02efe2e6 100644 --- a/server/src/services/resource-memberships.ts +++ b/server/src/services/resource-memberships.ts @@ -524,15 +524,19 @@ export function resourceMembershipService(db: Db, options: ResourceMembershipSer policySource: decision.source ?? "oss_default", }; } - await db.delete(documentMemberships).where(eq(documentMemberships.id, existing.id)); + const [deleted] = await db + .delete(documentMemberships) + .where(eq(documentMemberships.id, existing.id)) + .returning({ id: documentMemberships.id }); + const changed = deleted !== undefined; return { resourceType: "document", resourceId: input.documentId, state: "joined", starredAt: null, updatedAt: new Date(), - changed: true, - changeKind: "unstarred", + changed, + changeKind: changed ? "unstarred" : null, policySource: decision.source ?? "oss_default", }; },