Skip to content

Commit 46e7fb0

Browse files
authored
Merge branch 'dev' into custom-dashboards-and-unified-ai-no-playground
2 parents 91546bf + fe1b821 commit 46e7fb0

56 files changed

Lines changed: 2152 additions & 218 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stackframe/backend",
3-
"version": "2.8.74",
3+
"version": "2.8.75",
44
"repository": "https://github.com/stack-auth/stack-auth",
55
"private": true,
66
"type": "module",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ALTER TABLE "Project"
2+
ADD COLUMN "onboardingStatus" TEXT NOT NULL DEFAULT 'completed';
3+
4+
ALTER TABLE "Project"
5+
ADD CONSTRAINT "Project_onboardingStatus_valid"
6+
CHECK (
7+
"onboardingStatus" IN (
8+
'config_choice',
9+
'apps_selection',
10+
'auth_setup',
11+
'domain_setup',
12+
'email_theme_setup',
13+
'payments_setup',
14+
'completed'
15+
)
16+
) NOT VALID;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { randomUUID } from "crypto";
2+
import type { Sql } from "postgres";
3+
import { expect } from "vitest";
4+
5+
export const preMigration = async (sql: Sql) => {
6+
const projectId = `test-${randomUUID()}`;
7+
await sql`
8+
INSERT INTO "Project" ("id", "createdAt", "updatedAt", "displayName", "description", "isProductionMode")
9+
VALUES (${projectId}, NOW(), NOW(), 'Onboarding Test', '', false)
10+
`;
11+
return { projectId };
12+
};
13+
14+
export const postMigration = async (sql: Sql, ctx: Awaited<ReturnType<typeof preMigration>>) => {
15+
const rows = await sql`
16+
SELECT "onboardingStatus"
17+
FROM "Project"
18+
WHERE "id" = ${ctx.projectId}
19+
`;
20+
expect(rows).toHaveLength(1);
21+
expect(rows[0].onboardingStatus).toBe("completed");
22+
23+
const validProjectId = `test-${randomUUID()}`;
24+
await sql`
25+
INSERT INTO "Project" (
26+
"id",
27+
"createdAt",
28+
"updatedAt",
29+
"displayName",
30+
"description",
31+
"isProductionMode",
32+
"onboardingStatus"
33+
)
34+
VALUES (${validProjectId}, NOW(), NOW(), 'Valid Status Project', '', false, 'auth_setup')
35+
`;
36+
37+
const invalidProjectId = `test-${randomUUID()}`;
38+
await expect(sql`
39+
INSERT INTO "Project" (
40+
"id",
41+
"createdAt",
42+
"updatedAt",
43+
"displayName",
44+
"description",
45+
"isProductionMode",
46+
"onboardingStatus"
47+
)
48+
VALUES (${invalidProjectId}, NOW(), NOW(), 'Invalid Status Project', '', false, 'invalid_status')
49+
`).rejects.toThrow(/Project_onboardingStatus_valid/);
50+
51+
await expect(sql`
52+
UPDATE "Project"
53+
SET "onboardingStatus" = 'invalid_status'
54+
WHERE "id" = ${ctx.projectId}
55+
`).rejects.toThrow(/Project_onboardingStatus_valid/);
56+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE "Project"
2+
VALIDATE CONSTRAINT "Project_onboardingStatus_valid";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Sql } from "postgres";
2+
import { expect } from "vitest";
3+
4+
export const postMigration = async (sql: Sql) => {
5+
const rows = await sql`
6+
SELECT "convalidated"
7+
FROM "pg_constraint"
8+
WHERE "conname" = 'Project_onboardingStatus_valid'
9+
`;
10+
11+
expect(rows).toHaveLength(1);
12+
expect(rows[0].convalidated).toBe(true);
13+
};

apps/backend/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ model Project {
2626
description String @default("")
2727
isProductionMode Boolean
2828
ownerTeamId String? @db.Uuid
29+
onboardingStatus String @default("completed")
2930
3031
logoUrl String?
3132
logoFullUrl String?

apps/backend/src/lib/projects.tsx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export function getProjectQuery(projectId: string): RawQuery<Promise<Omit<Projec
6666
created_at_millis: new Date(row.createdAt + "Z").getTime(),
6767
is_production_mode: row.isProductionMode,
6868
owner_team_id: row.ownerTeamId,
69+
onboarding_status: row.onboardingStatus,
6970
};
7071
},
7172
};
@@ -101,22 +102,37 @@ export async function createOrUpdateProjectWithLegacyConfig(
101102
}
102103

103104
const [projectId, branchId] = await retryTransaction(globalPrismaClient, async (tx) => {
105+
const onboardingStatusColumnExistsRows = await tx.$queryRaw<Array<{ exists: boolean }>>`
106+
SELECT EXISTS (
107+
SELECT 1
108+
FROM information_schema.columns
109+
WHERE table_schema = 'public'
110+
AND table_name = 'Project'
111+
AND column_name = 'onboardingStatus'
112+
) AS "exists"
113+
`;
114+
const onboardingStatusColumnExists = onboardingStatusColumnExistsRows[0]?.exists === true;
115+
104116
let project: Prisma.ProjectGetPayload<{}>;
105117
let branchId: string;
106118
if (options.type === "create") {
107119
branchId = DEFAULT_BRANCH_ID;
120+
const createData: Prisma.ProjectCreateInput = {
121+
id: options.projectId ?? generateUuid(),
122+
displayName: options.data.display_name,
123+
description: options.data.description ?? "",
124+
isProductionMode: options.data.is_production_mode ?? false,
125+
ownerTeamId: options.data.owner_team_id,
126+
logoUrl: logoUrls['logo_url'],
127+
logoFullUrl: logoUrls['logo_full_url'],
128+
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
129+
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
130+
};
131+
if (onboardingStatusColumnExists && options.data.onboarding_status !== undefined) {
132+
createData.onboardingStatus = options.data.onboarding_status;
133+
}
108134
project = await tx.project.create({
109-
data: {
110-
id: options.projectId ?? generateUuid(),
111-
displayName: options.data.display_name,
112-
description: options.data.description ?? "",
113-
isProductionMode: options.data.is_production_mode ?? false,
114-
ownerTeamId: options.data.owner_team_id,
115-
logoUrl: logoUrls['logo_url'],
116-
logoFullUrl: logoUrls['logo_full_url'],
117-
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
118-
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
119-
},
135+
data: createData,
120136
});
121137

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

157+
const updateData: Prisma.ProjectUpdateInput = {
158+
displayName: options.data.display_name,
159+
description: options.data.description === null ? "" : options.data.description,
160+
isProductionMode: options.data.is_production_mode,
161+
logoUrl: logoUrls['logo_url'],
162+
logoFullUrl: logoUrls['logo_full_url'],
163+
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
164+
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
165+
};
166+
if (onboardingStatusColumnExists && options.data.onboarding_status !== undefined) {
167+
updateData.onboardingStatus = options.data.onboarding_status;
168+
}
169+
141170
project = await tx.project.update({
142171
where: {
143172
id: projectFound.id,
144173
},
145-
data: {
146-
displayName: options.data.display_name,
147-
description: options.data.description === null ? "" : options.data.description,
148-
isProductionMode: options.data.is_production_mode,
149-
logoUrl: logoUrls['logo_url'],
150-
logoFullUrl: logoUrls['logo_full_url'],
151-
logoDarkModeUrl: logoUrls['logo_dark_mode_url'],
152-
logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'],
153-
},
174+
data: updateData,
154175
});
155176
branchId = options.branchId;
156177
}

apps/dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stackframe/dashboard",
3-
"version": "2.8.74",
3+
"version": "2.8.75",
44
"repository": "https://github.com/stack-auth/stack-auth",
55
"private": true,
66
"scripts": {

0 commit comments

Comments
 (0)