Skip to content

Commit 012772d

Browse files
tefkah3mcd
andauthored
RFC: pubOp for creating and updating pubs more easily (#965)
* feat: new fluent pub mutation api * feat: add ability to replace pub relations * feat: beautiful better implementation * fix: cleanup a bit * feat: allow to properly purge orphans * fix: get rid of the concept of a pubId map and just pregenerate the ids * chore: add some comments * feat: add ability to move stages * fix: update test * feat: allow inline specification of pubops + cleanup of api * refactor: rename override option to 'replaceExisting' * test: add test for multiple relate * fix: fix type issues * refactor: add slightly better error handling * fix: add back deletePubValuesByValueId * fix: add back pubvalue upsert functions * fix: sort pubvalues deeply for tests * fix: fix tests and imports * feat: make upsert override by default * docs: add better documentation to orphan * refactor: simplify createPub logic a bit * chore: clean up * fix: update matcher types --------- Co-authored-by: Eric McDaniel <[email protected]>
1 parent 40074da commit 012772d

22 files changed

+3112
-190
lines changed

core/actions/_lib/runActionInstance.db.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const { getTrx, rollback, commit } = createForEachMockedTransaction();
1010

1111
const pubTriggerTestSeed = async () => {
1212
const slugName = `test-server-pub-${new Date().toISOString()}`;
13-
const { createSeed } = await import("~/prisma/seed/seedCommunity");
13+
const { createSeed } = await import("~/prisma/seed/createSeed");
1414

1515
return createSeed({
1616
community: {

core/app/api/v0/c/[communitySlug]/site/[...ts-rest]/route.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ const handler = createNextHandler(
373373
};
374374
},
375375
archive: async ({ params }) => {
376-
const { lastModifiedBy } = await checkAuthorization({
376+
const { lastModifiedBy, community } = await checkAuthorization({
377377
token: { scope: ApiAccessScope.pub, type: ApiAccessType.write },
378378
cookies: {
379379
capability: Capabilities.deletePub,
@@ -383,6 +383,7 @@ const handler = createNextHandler(
383383

384384
const result = await deletePub({
385385
pubId: params.pubId as PubsId,
386+
communityId: community.id,
386387
lastModifiedBy,
387388
});
388389

core/app/components/pubs/PubEditor/actions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export const removePub = defineServerAction(async function removePub({ pubId }:
178178
}
179179

180180
try {
181-
await deletePub({ pubId, lastModifiedBy });
181+
await deletePub({ pubId, lastModifiedBy, communityId: community.id });
182182

183183
return {
184184
success: true,

core/globalSetup.ts renamed to core/lib/__tests__/globalSetup.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { logger } from "logger";
77

88
export const setup = async () => {
99
config({
10-
path: ["./.env.test", "./.env.test.local"],
10+
path: [
11+
new URL("../../.env.test", import.meta.url).pathname,
12+
new URL("../../.env.test.local", import.meta.url).pathname,
13+
],
1114
});
1215

1316
if (process.env.SKIP_RESET) {

core/lib/__tests__/live.db.test.ts

+43-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,49 @@
11
import { describe, expect, test } from "vitest";
22

3+
import { CoreSchemaType, MemberRole } from "db/public";
4+
35
import type { ClientException } from "../serverActions";
6+
import { createSeed } from "~/prisma/seed/createSeed";
47
import { isClientException } from "../serverActions";
58
import { mockServerCode } from "./utils";
69

710
const { testDb, getLoginData, createForEachMockedTransaction } = await mockServerCode();
811

912
const { getTrx, rollback, commit } = createForEachMockedTransaction();
1013

14+
const communitySeed = createSeed({
15+
community: {
16+
name: "test",
17+
slug: "test",
18+
},
19+
pubFields: {
20+
Title: { schemaName: CoreSchemaType.String },
21+
},
22+
pubTypes: {
23+
"Basic Pub": {
24+
Title: { isTitle: true },
25+
},
26+
},
27+
users: {
28+
admin: {
29+
role: MemberRole.admin,
30+
},
31+
},
32+
pubs: [
33+
{
34+
pubType: "Basic Pub",
35+
values: {
36+
Title: "test",
37+
},
38+
},
39+
],
40+
});
41+
42+
const seed = async (trx = testDb) => {
43+
const { seedCommunity } = await import("~/prisma/seed/seedCommunity");
44+
return seedCommunity(communitySeed, undefined, trx);
45+
};
46+
1147
describe("live", () => {
1248
test("should be able to connect to db", async () => {
1349
const result = await testDb.selectFrom("users").selectAll().execute();
@@ -17,6 +53,8 @@ describe("live", () => {
1753
test("can rollback transactions", async () => {
1854
const trx = getTrx();
1955

56+
const { community, users, pubs } = await seed(trx);
57+
2058
// Insert a user
2159
const user = await trx
2260
.insertInto("users")
@@ -58,6 +96,7 @@ describe("live", () => {
5896
describe("transaction block example", () => {
5997
test("can add a user that will not persist", async () => {
6098
const trx = getTrx();
99+
61100
await trx
62101
.insertInto("users")
63102
.values({
@@ -79,6 +118,9 @@ describe("live", () => {
79118

80119
test("createForm needs a logged in user", async () => {
81120
const trx = getTrx();
121+
122+
const { community, users, pubs } = await seed(trx);
123+
82124
getLoginData.mockImplementation(() => {
83125
return undefined;
84126
});
@@ -87,12 +129,6 @@ describe("live", () => {
87129
(m) => m.createForm
88130
);
89131

90-
const community = await trx
91-
.selectFrom("communities")
92-
.selectAll()
93-
.where("slug", "=", "croccroc")
94-
.executeTakeFirstOrThrow();
95-
96132
const pubType = await trx
97133
.selectFrom("pub_types")
98134
.select(["id"])
@@ -110,15 +146,11 @@ describe("live", () => {
110146
return { id: "123", isSuperAdmin: true };
111147
});
112148

149+
const { community, users, pubs } = await seed(trx);
113150
const getForm = await import("../server/form").then((m) => m.getForm);
114151
const createForm = await import("~/app/c/[communitySlug]/forms/actions").then(
115152
(m) => m.createForm
116153
);
117-
const community = await trx
118-
.selectFrom("communities")
119-
.selectAll()
120-
.where("slug", "=", "croccroc")
121-
.executeTakeFirstOrThrow();
122154

123155
const forms = await getForm({ slug: "my-form-2", communityId: community.id }).execute();
124156
expect(forms.length).toEqual(0);

core/lib/__tests__/matchers.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { expect } from "vitest";
2+
3+
import type { ProcessedPub } from "contracts";
4+
import type { PubsId } from "db/public";
5+
6+
import type { db } from "~/kysely/database";
7+
8+
const deepSortValues = (pub: ProcessedPub): ProcessedPub => {
9+
pub.values
10+
.sort((a, b) => (a.value as string).localeCompare(b.value as string))
11+
.map((item) => ({
12+
...item,
13+
relatedPub: item.relatedPub?.values ? deepSortValues(item.relatedPub) : item.relatedPub,
14+
}));
15+
16+
return pub;
17+
};
18+
19+
expect.extend({
20+
async toExist(received: PubsId, expected?: typeof db) {
21+
const { getPlainPub } = await import("../server/pub");
22+
23+
const pub = await getPlainPub(received, expected).executeTakeFirst();
24+
const pass = Boolean(pub && pub.id === received);
25+
const { isNot } = this;
26+
27+
return {
28+
pass,
29+
message: () =>
30+
isNot
31+
? `Expected pub with ID ${received} not to exist, but it ${pass ? "does" : "does not"}`
32+
: `Expected pub with ID ${received} to exist, but it ${pass ? "does" : "does not"}`,
33+
};
34+
},
35+
36+
toHaveValues(received: ProcessedPub, expected: Partial<ProcessedPub["values"][number]>[]) {
37+
const pub = received;
38+
const sortedPubValues = deepSortValues(pub);
39+
40+
const expectedLength = expected.length;
41+
const receivedLength = sortedPubValues.values.length;
42+
43+
const isNot = this.isNot;
44+
if (!isNot && !this.equals(expectedLength, receivedLength)) {
45+
return {
46+
pass: false,
47+
message: () =>
48+
`Expected pub to have ${expectedLength} values, but it has ${receivedLength}`,
49+
};
50+
}
51+
52+
// equiv. to .toMatchObject
53+
const pass = this.equals(sortedPubValues.values, expected, [
54+
this.utils.iterableEquality,
55+
this.utils.subsetEquality,
56+
]);
57+
58+
return {
59+
pass,
60+
message: () =>
61+
pass
62+
? `Expected pub ${isNot ? "not" : ""} to have values ${JSON.stringify(expected)}, and it does ${isNot ? "not" : ""}`
63+
: `Expected pub ${isNot ? "not to" : "to"} match values ${this.utils.diff(sortedPubValues.values, expected)}`,
64+
};
65+
},
66+
});

core/lib/server/pub-capabilities.db.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { describe, expect, it } from "vitest";
22

33
import { CoreSchemaType, MemberRole } from "db/public";
44

5-
import type { Seed } from "~/prisma/seed/seedCommunity";
5+
import { createSeed } from "~/prisma/seed/createSeed";
66
import { mockServerCode } from "../__tests__/utils";
77

88
await mockServerCode();
99

10-
const seed = {
10+
const seed = createSeed({
1111
community: {
1212
name: "test-pub-capabilities",
1313
slug: "test-pub-capabilities",
@@ -97,7 +97,7 @@ const seed = {
9797
},
9898
},
9999
],
100-
} as Seed;
100+
});
101101

102102
describe("getPubsWithRelatedValuesAndChildren capabilities", () => {
103103
it("should restrict pubs by visibility", async () => {

0 commit comments

Comments
 (0)