diff --git a/apps/studio/prisma/generated/generatedTypes.ts b/apps/studio/prisma/generated/generatedTypes.ts index fc0130e34b..b8ef53380a 100644 --- a/apps/studio/prisma/generated/generatedTypes.ts +++ b/apps/studio/prisma/generated/generatedTypes.ts @@ -46,7 +46,7 @@ export interface Resource { permalink: string siteId: number parentId: string | null - mainBlobId: string | null + versionId: string | null draftBlobId: string | null state: Generated type: ResourceType @@ -77,6 +77,13 @@ export interface VerificationToken { attempts: Generated expires: Timestamp } +export interface Version { + id: GeneratedAlways + versionNum: string + resourceId: string + blobId: string + publishedAt: Generated +} export interface DB { Blob: Blob Footer: Footer @@ -87,4 +94,5 @@ export interface DB { SiteMember: SiteMember User: User VerificationToken: VerificationToken + Version: Version } diff --git a/apps/studio/prisma/generated/selectableTypes.ts b/apps/studio/prisma/generated/selectableTypes.ts index 558f14bb1e..1bca01e5c2 100644 --- a/apps/studio/prisma/generated/selectableTypes.ts +++ b/apps/studio/prisma/generated/selectableTypes.ts @@ -12,3 +12,4 @@ export type Site = Selectable export type SiteMember = Selectable export type User = Selectable export type VerificationToken = Selectable +export type Version = Selectable diff --git a/apps/studio/prisma/migrations/20240725140719_add_versions/migration.sql b/apps/studio/prisma/migrations/20240725140719_add_versions/migration.sql new file mode 100644 index 0000000000..f59c6e8560 --- /dev/null +++ b/apps/studio/prisma/migrations/20240725140719_add_versions/migration.sql @@ -0,0 +1,35 @@ +/* + Warnings: + + - You are about to drop the column `mainBlobId` on the `Resource` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Resource" DROP CONSTRAINT "Resource_mainBlobId_fkey"; + +-- DropIndex +DROP INDEX "Resource_mainBlobId_key"; + +-- AlterTable +ALTER TABLE "Resource" DROP COLUMN "mainBlobId", +ADD COLUMN "versionId" BIGINT; + +-- CreateTable +CREATE TABLE "Version" ( + "id" BIGSERIAL NOT NULL, + "versionNum" BIGINT NOT NULL, + "resourceId" BIGINT NOT NULL, + "blobId" BIGINT NOT NULL, + "publishedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Version_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Version_resourceId_key" ON "Version"("resourceId"); + +-- AddForeignKey +ALTER TABLE "Version" ADD CONSTRAINT "Version_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "Resource"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Version" ADD CONSTRAINT "Version_blobId_fkey" FOREIGN KEY ("blobId") REFERENCES "Blob"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/studio/prisma/schema.prisma b/apps/studio/prisma/schema.prisma index 66d0b3e65d..b2a454166b 100644 --- a/apps/studio/prisma/schema.prisma +++ b/apps/studio/prisma/schema.prisma @@ -33,6 +33,16 @@ model VerificationToken { expires DateTime } +model Version { + id BigInt @id @default(autoincrement()) + versionNum BigInt + resourceId BigInt @unique + resource Resource @relation("CurrentVersion", fields: [resourceId], references: [id]) + blobId BigInt + blob Blob @relation(fields: [blobId], references: [id]) + publishedAt DateTime @default(now()) +} + model Resource { id BigInt @id @default(autoincrement()) title String @@ -46,16 +56,25 @@ model Resource { permission Permission[] - mainBlob Blob? @relation("MainBlob", fields: [mainBlobId], references: [id]) - mainBlobId BigInt? @unique + versionId BigInt? + version Version? @relation("CurrentVersion") - draftBlob Blob? @relation("DraftBlob", fields: [draftBlobId], references: [id]) draftBlobId BigInt? @unique + draftBlob Blob? @relation(fields: [draftBlobId], references: [id]) state ResourceState? @default(Draft) type ResourceType } +model Blob { + id BigInt @id @default(autoincrement()) + /// @kyselyType(PrismaJson.BlobJsonContent) + /// [BlobJsonContent] + content Json + versions Version[] + draftResource Resource? +} + enum ResourceState { Draft Published @@ -124,16 +143,6 @@ model Footer { content Json } -model Blob { - id BigInt @id @default(autoincrement()) - /// @kyselyType(PrismaJson.BlobJsonContent) - /// [BlobJsonContent] - content Json - - mainResource Resource? @relation("MainBlob") - draftResource Resource? @relation("DraftBlob") -} - model SiteMember { userId String user User @relation(fields: [userId], references: [id]) diff --git a/apps/studio/prisma/seed.ts b/apps/studio/prisma/seed.ts index 66f3f4aeb0..997fd3e77f 100644 --- a/apps/studio/prisma/seed.ts +++ b/apps/studio/prisma/seed.ts @@ -223,7 +223,7 @@ async function main() { await db .insertInto("Resource") .values({ - mainBlobId: String(blobId), + draftBlobId: String(blobId), permalink: "home", siteId, type: "Page", @@ -232,8 +232,8 @@ async function main() { .onConflict((oc) => oc - .column("mainBlobId") - .doUpdateSet((eb) => ({ mainBlobId: eb.ref("excluded.mainBlobId") })), + .column("draftBlobId") + .doUpdateSet((eb) => ({ draftBlobId: eb.ref("excluded.draftBlobId") })), ) .executeTakeFirstOrThrow() diff --git a/apps/studio/src/server/modules/folder/folder.router.ts b/apps/studio/src/server/modules/folder/folder.router.ts index 31596044ae..e2e949b275 100644 --- a/apps/studio/src/server/modules/folder/folder.router.ts +++ b/apps/studio/src/server/modules/folder/folder.router.ts @@ -26,7 +26,7 @@ export const folderRouter = router({ .where("parentId", "=", String(input.resourceId)) .execute() const children = childrenResult.map((c) => { - if (c.draftBlobId || c.mainBlobId) { + if (c.draftBlobId || c.versionId) { return { id: c.id, permalink: c.permalink, diff --git a/apps/studio/src/server/modules/page/page.router.ts b/apps/studio/src/server/modules/page/page.router.ts index cf3aa3cf55..df3f3b27d3 100644 --- a/apps/studio/src/server/modules/page/page.router.ts +++ b/apps/studio/src/server/modules/page/page.router.ts @@ -69,7 +69,7 @@ export const pageRouter = router({ "Resource.id", "Resource.permalink", "Resource.title", - "Resource.mainBlobId", + "Resource.versionId", "Resource.draftBlobId", "Resource.type", ]) diff --git a/apps/studio/src/server/modules/resource/resource.service.ts b/apps/studio/src/server/modules/resource/resource.service.ts index 0d22a2624c..d4e59d49e5 100644 --- a/apps/studio/src/server/modules/resource/resource.service.ts +++ b/apps/studio/src/server/modules/resource/resource.service.ts @@ -11,7 +11,7 @@ const defaultResourceSelect: SelectExpression[] = [ "Resource.permalink", "Resource.siteId", "Resource.parentId", - "Resource.mainBlobId", + "Resource.versionId", "Resource.draftBlobId", "Resource.type", "Resource.state", @@ -81,8 +81,9 @@ export const getFullPageById = async (args: { } return getById(args) - .where("Resource.mainBlobId", "is not", null) - .innerJoin("Blob", "Resource.mainBlobId", "Blob.id") + .where("Resource.versionId", "is not", null) + .innerJoin("Version", "Resource.versionId", "Version.id") + .innerJoin("Blob", "Version.blobId", "Blob.id") .select(defaultResourceWithBlobSelect) .executeTakeFirst() } diff --git a/apps/studio/src/server/modules/resource/resource.types.ts b/apps/studio/src/server/modules/resource/resource.types.ts index 8e9c7227a9..4a7a0b2d6d 100644 --- a/apps/studio/src/server/modules/resource/resource.types.ts +++ b/apps/studio/src/server/modules/resource/resource.types.ts @@ -2,7 +2,6 @@ import { type IsomerPageSchemaType, type IsomerSiteProps, } from "@opengovsg/isomer-components" -import { type SetRequired } from "type-fest" import type { Resource } from "~server/db" @@ -12,7 +11,7 @@ export type PageContent = Omit< > // TODO: Technically mainBlobId is not required before 1st publish -export type Page = SetRequired +export type Page = Resource export interface Navbar { items: IsomerSiteProps["navBarItems"] diff --git a/apps/studio/tests/msw/handlers/page.ts b/apps/studio/tests/msw/handlers/page.ts index 898ecdd6cc..1c6b841fdd 100644 --- a/apps/studio/tests/msw/handlers/page.ts +++ b/apps/studio/tests/msw/handlers/page.ts @@ -13,23 +13,23 @@ const pageListQuery = (wait?: DelayMode | number) => { id: "4", permalink: "test-page-1", title: "Test page 1", - mainBlobId: "3", - draftBlobId: null, + versionId: null, + draftBlobId: "3", type: "Page", }, { id: "5", permalink: "test-page-2", title: "Test page 2", - mainBlobId: "4", - draftBlobId: null, + versionId: null, + draftBlobId: "4", type: "Page", }, { id: "6", permalink: "folder", title: "Test folder 1", - mainBlobId: null, + versionId: null, draftBlobId: null, type: "Folder", },