diff --git a/core/app/components/pubs/PubEditor/PubEditorClient.tsx b/core/app/components/pubs/PubEditor/PubEditorClient.tsx index 9187c0718..5b066e78c 100644 --- a/core/app/components/pubs/PubEditor/PubEditorClient.tsx +++ b/core/app/components/pubs/PubEditor/PubEditorClient.tsx @@ -78,8 +78,14 @@ type InferFormValues = T extends UseFormReturn ? V : never; export function PubEditorClient(props: Props) { const hasValues = Object.keys(props.pubValues).length > 0; const paramString = hasValues ? "update" : "create"; - const runCreatePub = useServerAction(actions.createPub); + const runCreatePub = useServerAction(actions.createPubRecursive); const runUpdatePub = useServerAction(actions.updatePub); + const availableStages = [ + { id: undefined, name: "No stage" }, + ...props.availableStages.map((s) => { + return { id: s.id, name: s.name }; + }), + ]; const path = usePathname(); const router = useRouter(); @@ -171,12 +177,14 @@ export function PubEditorClient(props: Props) { } const result = await runCreatePub({ - pubId, + body: { + id: pubId, + pubTypeId: pubTypeId as PubTypesId, + stageId: stageId as StagesId, + values: enabledPubValues as Record, + }, communityId: props.communityId, - parentId: props.parentId, - pubTypeId: pubTypeId as PubTypesId, - pubValues: enabledPubValues, - stageId: stageId as StagesId, + parent: props.parentId ? { id: props.parentId } : undefined, }); if (didSucceed(result)) { toast({ @@ -251,15 +259,15 @@ export function PubEditorClient(props: Props) { data-testid="stage-selector" > {field.value - ? props.availableStages.find( + ? availableStages.find( (stage) => stage.id === field.value )?.name - : "Select Stage"} + : "No stage"} - {props.availableStages.map((stage) => ( + {availableStages.map((stage) => ( { diff --git a/core/app/components/pubs/PubEditor/actions.ts b/core/app/components/pubs/PubEditor/actions.ts index b50f44b34..18e0fab92 100644 --- a/core/app/components/pubs/PubEditor/actions.ts +++ b/core/app/components/pubs/PubEditor/actions.ts @@ -10,85 +10,26 @@ import { validatePubValuesBySchemaName } from "~/actions/_lib/validateFields"; import { db } from "~/kysely/database"; import { getLoginData } from "~/lib/auth/loginData"; import { isCommunityAdmin } from "~/lib/auth/roles"; +import { createPubRecursiveNew } from "~/lib/server"; import { autoCache } from "~/lib/server/cache/autoCache"; import { autoRevalidate } from "~/lib/server/cache/autoRevalidate"; import { defineServerAction } from "~/lib/server/defineServerAction"; -export const createPub = defineServerAction(async function createPub({ - communityId, - stageId, - pubTypeId, - pubValues, - path, - parentId, - pubId, -}: { - communityId: CommunitiesId; - stageId: StagesId; - pubTypeId: PubTypesId; - pubValues: PubValues; - path?: string | null; - parentId?: PubsId; - pubId?: PubsId; -}) { +export const createPubRecursive = defineServerAction(async function createPubRecursive( + ...[props]: Parameters +) { const { user } = await getLoginData(); if (!user) { throw new Error("Not logged in"); } - if (!isCommunityAdmin(user, { id: communityId })) { + if (!isCommunityAdmin(user, { id: props.communityId })) { return { error: "You need to be an admin", }; } - - const pubValueEntries = Object.entries(pubValues); - try { - const query = db - .with("new_pub", (db) => - db - .insertInto("pubs") - .values({ - id: pubId, - communityId: communityId, - pubTypeId: pubTypeId, - parentId: parentId, - }) - .returning("id") - ) - .with("stage_create", (db) => - db.insertInto("PubsInStages").values((eb) => ({ - pubId: eb.selectFrom("new_pub").select("new_pub.id"), - stageId, - })) - ); - - await autoRevalidate( - // Running an `insertInto` with an empty array results in a SQL syntax - // error. I was not able to find a way to generate the insert into - // statement only if the array has on or more values. So I extracted the - // CTEs into a partial query builder above, then conditionally attach the - // insertInto if there are values, otherwise perform a null select to - // produce valid SQL. - pubValueEntries.length > 0 - ? query.insertInto("pub_values").values((eb) => - pubValueEntries.map(([pubFieldSlug, pubValue]) => ({ - pubId: eb.selectFrom("new_pub").select("new_pub.id"), - value: JSON.stringify(pubValue), - fieldId: eb - .selectFrom("pub_fields") - .where("pub_fields.slug", "=", pubFieldSlug) - .select("pub_fields.id"), - })) - ) - : query.selectFrom("new_pub").select([]) - ).execute(); - - if (path) { - revalidatePath(path); - } - + await createPubRecursiveNew(props); return { success: true, report: `Successfully created a new Pub`, diff --git a/core/app/components/pubs/PubEditor/helpers.ts b/core/app/components/pubs/PubEditor/helpers.ts index 20342b284..123c172b8 100644 --- a/core/app/components/pubs/PubEditor/helpers.ts +++ b/core/app/components/pubs/PubEditor/helpers.ts @@ -87,7 +87,7 @@ export const createPubEditorSchemaFromPubFields = ( return Type.Object<{ pubTypeId: TString; stageId: TString }>({ pubTypeId: Type.String({ format: "uuid" }), - stageId: Type.String({ format: "uuid" }), + stageId: Type.Optional(Type.String({ format: "uuid" })), ...pubFieldSchemasBySlug, }); }; diff --git a/core/lib/server/pub.ts b/core/lib/server/pub.ts index 578250add..fb0b91b08 100644 --- a/core/lib/server/pub.ts +++ b/core/lib/server/pub.ts @@ -465,19 +465,21 @@ export const createPubRecursiveNew = async ({ - // not sure this is the best way to do this - fieldId: id, - pubId: newPub.id, - value: value, - })) - ) - .returningAll() - ).execute(); + const pubValues = valuesWithFieldIds.length + ? await autoRevalidate( + trx + .insertInto("pub_values") + .values( + valuesWithFieldIds.map(({ id, value }, index) => ({ + // not sure this is the best way to do this + fieldId: id, + pubId: newPub.id, + value: value, + })) + ) + .returningAll() + ).execute() + : []; if (!body.children) { return { diff --git a/core/playwright/pub.spec.ts b/core/playwright/pub.spec.ts index e8360773c..66ffa8ce0 100644 --- a/core/playwright/pub.spec.ts +++ b/core/playwright/pub.spec.ts @@ -59,3 +59,49 @@ test.describe("Moving a pub", () => { await expect(page.getByRole("button", { name: "Move" })).toHaveCount(0); }); }); + +test.describe("Creating a pub", () => { + test("Can create a pub without a stage", async () => { + const pubsPage = new PubsPage(page, COMMUNITY_SLUG); + const title = "Pub without a stage"; + await pubsPage.goTo(); + await page.getByRole("button", { name: "Create" }).click(); + await page.getByLabel("Title").fill(title); + await page.getByLabel("Content").fill("Some content"); + await page.getByRole("button", { name: "Create Pub" }).click(); + await page.getByRole("link", { name: title }).click(); + await page.waitForURL(/.*\/c\/.+\/pubs\/.+/); + await expect(page.getByTestId("current-stage")).toHaveCount(0); + }); + + test("Can create a pub with a stage", async () => { + const pubsPage = new PubsPage(page, COMMUNITY_SLUG); + const title = "Pub with a stage"; + const stage = "Submitted"; + await pubsPage.goTo(); + await page.getByRole("button", { name: "Create" }).click(); + await page.getByLabel("Title").fill(title); + await page.getByLabel("Content").fill("Some content"); + await page.getByRole("button", { name: "No stage" }).click(); + await page.getByRole("menuitem", { name: stage }).click(); + await page.getByRole("button", { name: "Create Pub" }).click(); + await page.getByRole("link", { name: title }).click(); + await page.waitForURL(/.*\/c\/.+\/pubs\/.+/); + await expect(page.getByTestId("current-stage")).toHaveText(stage); + }); + + test("Can create a pub with no values", async () => { + const pubsPage = new PubsPage(page, COMMUNITY_SLUG); + await pubsPage.goTo(); + await page.getByRole("button", { name: "Create" }).click(); + await page.getByLabel("Title").fill("asdf"); + const toggles = await page.getByLabel("Toggle field").all(); + for (const toggle of toggles) { + await toggle.click(); + } + await page.getByRole("button", { name: "Create Pub" }).click(); + await expect(page.getByRole("status").filter({ hasText: "New pub created" })).toHaveCount( + 1 + ); + }); +});