Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ALTER TABLE "Project"
ADD COLUMN "onboardingStatus" TEXT NOT NULL DEFAULT 'completed';

ALTER TABLE "Project"
ADD CONSTRAINT "Project_onboardingStatus_valid"
CHECK (
"onboardingStatus" IN (
'config_choice',
'apps_selection',
'auth_setup',
'domain_setup',
'email_theme_setup',
'payments_setup',
'completed'
)
) NOT VALID;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { randomUUID } from "crypto";
import type { Sql } from "postgres";
import { expect } from "vitest";

export const preMigration = async (sql: Sql) => {
const projectId = `test-${randomUUID()}`;
await sql`
INSERT INTO "Project" ("id", "createdAt", "updatedAt", "displayName", "description", "isProductionMode")
VALUES (${projectId}, NOW(), NOW(), 'Onboarding Test', '', false)
`;
return { projectId };
};

export const postMigration = async (sql: Sql, ctx: Awaited<ReturnType<typeof preMigration>>) => {
const rows = await sql`
SELECT "onboardingStatus"
FROM "Project"
WHERE "id" = ${ctx.projectId}
`;
expect(rows).toHaveLength(1);
expect(rows[0].onboardingStatus).toBe("completed");

const validProjectId = `test-${randomUUID()}`;
await sql`
INSERT INTO "Project" (
"id",
"createdAt",
"updatedAt",
"displayName",
"description",
"isProductionMode",
"onboardingStatus"
)
VALUES (${validProjectId}, NOW(), NOW(), 'Valid Status Project', '', false, 'auth_setup')
`;

const invalidProjectId = `test-${randomUUID()}`;
await expect(sql`
INSERT INTO "Project" (
"id",
"createdAt",
"updatedAt",
"displayName",
"description",
"isProductionMode",
"onboardingStatus"
)
VALUES (${invalidProjectId}, NOW(), NOW(), 'Invalid Status Project', '', false, 'invalid_status')
`).rejects.toThrow(/Project_onboardingStatus_valid/);

await expect(sql`
UPDATE "Project"
SET "onboardingStatus" = 'invalid_status'
WHERE "id" = ${ctx.projectId}
`).rejects.toThrow(/Project_onboardingStatus_valid/);
};
Comment thread
Developing-Gamer marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "Project"
VALIDATE CONSTRAINT "Project_onboardingStatus_valid";
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Sql } from "postgres";
import { expect } from "vitest";

export const postMigration = async (sql: Sql) => {
const rows = await sql`
SELECT "convalidated"
FROM "pg_constraint"
WHERE "conname" = 'Project_onboardingStatus_valid'
`;

expect(rows).toHaveLength(1);
expect(rows[0].convalidated).toBe(true);
};
1 change: 1 addition & 0 deletions apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ model Project {
description String @default("")
isProductionMode Boolean
ownerTeamId String? @db.Uuid
onboardingStatus String @default("completed")

logoUrl String?
logoFullUrl String?
Expand Down
61 changes: 41 additions & 20 deletions apps/backend/src/lib/projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function getProjectQuery(projectId: string): RawQuery<Promise<Omit<Projec
created_at_millis: new Date(row.createdAt + "Z").getTime(),
is_production_mode: row.isProductionMode,
owner_team_id: row.ownerTeamId,
onboarding_status: row.onboardingStatus,
};
},
};
Expand Down Expand Up @@ -101,22 +102,37 @@ export async function createOrUpdateProjectWithLegacyConfig(
}

const [projectId, branchId] = await retryTransaction(globalPrismaClient, async (tx) => {
const onboardingStatusColumnExistsRows = await tx.$queryRaw<Array<{ exists: boolean }>>`
SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'Project'
AND column_name = 'onboardingStatus'
) AS "exists"
`;
const onboardingStatusColumnExists = onboardingStatusColumnExistsRows[0]?.exists === true;

let project: Prisma.ProjectGetPayload<{}>;
let branchId: string;
if (options.type === "create") {
branchId = DEFAULT_BRANCH_ID;
const createData: Prisma.ProjectCreateInput = {
id: options.projectId ?? generateUuid(),
displayName: options.data.display_name,
description: options.data.description ?? "",
isProductionMode: options.data.is_production_mode ?? false,
ownerTeamId: options.data.owner_team_id,
logoUrl: logoUrls['logo_url'],
logoFullUrl: logoUrls['logo_full_url'],
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
};
if (onboardingStatusColumnExists && options.data.onboarding_status !== undefined) {
createData.onboardingStatus = options.data.onboarding_status;
}
project = await tx.project.create({
data: {
id: options.projectId ?? generateUuid(),
displayName: options.data.display_name,
description: options.data.description ?? "",
isProductionMode: options.data.is_production_mode ?? false,
ownerTeamId: options.data.owner_team_id,
logoUrl: logoUrls['logo_url'],
logoFullUrl: logoUrls['logo_full_url'],
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
},
data: createData,
});

await tx.tenancy.create({
Expand All @@ -138,19 +154,24 @@ export async function createOrUpdateProjectWithLegacyConfig(
throw new KnownErrors.ProjectNotFound(options.projectId);
}

const updateData: Prisma.ProjectUpdateInput = {
displayName: options.data.display_name,
description: options.data.description === null ? "" : options.data.description,
isProductionMode: options.data.is_production_mode,
logoUrl: logoUrls['logo_url'],
logoFullUrl: logoUrls['logo_full_url'],
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
};
if (onboardingStatusColumnExists && options.data.onboarding_status !== undefined) {
updateData.onboardingStatus = options.data.onboarding_status;
}

project = await tx.project.update({
where: {
id: projectFound.id,
},
data: {
displayName: options.data.display_name,
description: options.data.description === null ? "" : options.data.description,
isProductionMode: options.data.is_production_mode,
logoUrl: logoUrls['logo_url'],
logoFullUrl: logoUrls['logo_full_url'],
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
},
data: updateData,
});
branchId = options.branchId;
}
Expand Down
Loading
Loading