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,18 @@
-- Add 'welcome' to the allowed onboarding status values.
-- Drop the old constraint and add a new one (NOT VALID for speed),
-- then validate in the next migration.
ALTER TABLE "Project"
DROP CONSTRAINT "Project_onboardingStatus_valid",
ADD CONSTRAINT "Project_onboardingStatus_valid"
CHECK (
"onboardingStatus" IN (
'config_choice',
'apps_selection',
'auth_setup',
'domain_setup',
'email_theme_setup',
'payments_setup',
'welcome',
'completed'
)
) NOT VALID;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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", "onboardingStatus"
)
VALUES (${projectId}, NOW(), NOW(), 'Welcome Test', '', false, 'completed')
`;

await expect(sql`
UPDATE "Project"
SET "onboardingStatus" = 'welcome'
WHERE "id" = ${projectId}
`).rejects.toThrow(/Project_onboardingStatus_valid/);

return { projectId };
};

export const postMigration = async (sql: Sql, ctx: Awaited<ReturnType<typeof preMigration>>) => {
await sql`
UPDATE "Project"
SET "onboardingStatus" = 'welcome'
WHERE "id" = ${ctx.projectId}
`;

const rows = await sql`
SELECT "onboardingStatus"
FROM "Project"
WHERE "id" = ${ctx.projectId}
`;
expect(rows).toHaveLength(1);
expect(rows[0].onboardingStatus).toBe("welcome");

const insertWelcomeProjectId = `test-${randomUUID()}`;
await sql`
INSERT INTO "Project" (
"id", "createdAt", "updatedAt", "displayName", "description",
"isProductionMode", "onboardingStatus"
)
VALUES (${insertWelcomeProjectId}, NOW(), NOW(), 'Welcome Insert Test', '', false, 'welcome')
`;

const insertedRows = await sql`
SELECT "onboardingStatus"
FROM "Project"
WHERE "id" = ${insertWelcomeProjectId}
`;
expect(insertedRows).toHaveLength(1);
expect(insertedRows[0].onboardingStatus).toBe("welcome");

const insertInvalidProjectId = `test-${randomUUID()}`;
await expect(sql`
INSERT INTO "Project" (
"id", "createdAt", "updatedAt", "displayName", "description",
"isProductionMode", "onboardingStatus"
)
VALUES (${insertInvalidProjectId}, NOW(), NOW(), 'Invalid Status Insert', '', 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
N2D4 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,12 @@
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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ import { LOCAL_EMULATOR_ENV_CONFIG_BLOCKED_MESSAGE, isLocalEmulatorProject } fro
import { globalPrismaClient, rawQuery } from "@/prisma-client";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { branchConfigSchema, environmentConfigSchema, getConfigOverrideErrors, migrateConfigOverride, projectConfigSchema } from "@stackframe/stack-shared/dist/config/schema";
import { adaptSchema, adminAuthTypeSchema, branchConfigSourceSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { adaptSchema, branchConfigSourceSchema, serverOrHigherAuthTypeSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StackAssertionError, StatusError, captureError } from "@stackframe/stack-shared/dist/utils/errors";
import * as yup from "yup";
type BranchConfigSourceApi = yup.InferType<typeof branchConfigSourceSchema>;

const levelSchema = yupString().oneOf(["project", "branch", "environment"]).defined();

function assertServerAccessAllowed(accessType: "server" | "admin", level: yup.InferType<typeof levelSchema>) {
if (accessType === "server" && level !== "branch") {
throw new StatusError(StatusError.Forbidden, "Server access can only manage branch config overrides.");
}
}

function shouldEnqueueExternalDbSync(config: unknown): boolean {
if (!config || typeof config !== "object") return false;
const configRecord = config as Record<string, unknown>;
Expand Down Expand Up @@ -124,7 +130,7 @@ export const GET = createSmartRouteHandler({
},
request: yupObject({
auth: yupObject({
type: adminAuthTypeSchema,
type: serverOrHigherAuthTypeSchema,
tenancy: adaptSchema,
}).defined(),
params: yupObject({
Expand All @@ -139,6 +145,8 @@ export const GET = createSmartRouteHandler({
}).defined(),
}),
handler: async (req) => {
assertServerAccessAllowed(req.auth.type, req.params.level);

const levelConfig = levelConfigs[req.params.level];
const config = await levelConfig.get({
projectId: req.auth.tenancy.project.id,
Expand Down Expand Up @@ -207,7 +215,7 @@ export const PUT = createSmartRouteHandler({
},
request: yupObject({
auth: yupObject({
type: adminAuthTypeSchema,
type: serverOrHigherAuthTypeSchema,
tenancy: adaptSchema,
}).defined(),
params: yupObject({
Expand All @@ -221,6 +229,8 @@ export const PUT = createSmartRouteHandler({
}),
response: writeResponseSchema,
handler: async (req) => {
assertServerAccessAllowed(req.auth.type, req.params.level);

if (req.params.level === "environment" && await isLocalEmulatorProject(req.auth.tenancy.project.id)) {
throw new StatusError(StatusError.BadRequest, LOCAL_EMULATOR_ENV_CONFIG_BLOCKED_MESSAGE);
}
Expand Down Expand Up @@ -266,7 +276,7 @@ export const PATCH = createSmartRouteHandler({
},
request: yupObject({
auth: yupObject({
type: adminAuthTypeSchema,
type: serverOrHigherAuthTypeSchema,
tenancy: adaptSchema,
}).defined(),
params: yupObject({
Expand All @@ -278,6 +288,8 @@ export const PATCH = createSmartRouteHandler({
}),
response: writeResponseSchema,
handler: async (req) => {
assertServerAccessAllowed(req.auth.type, req.params.level);

if (req.params.level === "environment" && await isLocalEmulatorProject(req.auth.tenancy.project.id)) {
throw new StatusError(StatusError.BadRequest, LOCAL_EMULATOR_ENV_CONFIG_BLOCKED_MESSAGE);
}
Expand Down
11 changes: 9 additions & 2 deletions apps/backend/src/app/api/latest/internal/payments/setup/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,21 @@ export const POST = createSmartRouteHandler({
}),
handler: async ({ auth }) => {
const stripe = getStackStripe();
const dashboardBaseUrl = getEnvVariable("NEXT_PUBLIC_STACK_DASHBOARD_URL");

const project = await globalPrismaClient.project.findUnique({
where: { id: auth.project.id },
select: { stripeAccountId: true },
select: { onboardingStatus: true, stripeAccountId: true },
});

let stripeAccountId = project?.stripeAccountId || null;
const returnToUrl = new URL(`/projects/${auth.project.id}/payments`, getEnvVariable("NEXT_PUBLIC_STACK_DASHBOARD_URL")).toString();
const returnToUrl = project?.onboardingStatus === "payments_setup"
? (() => {
const onboardingUrl = new URL("/new-project", dashboardBaseUrl);
onboardingUrl.searchParams.set("project_id", auth.project.id);
return onboardingUrl.toString();
})()
: new URL(`/projects/${encodeURIComponent(auth.project.id)}/payments`, dashboardBaseUrl).toString();

if (!stripeAccountId) {
const account = await stripe.accounts.create({
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"geist": "^1",
"input-otp": "^1.4.1",
"jose": "^6.1.3",
"libsodium-wrappers": "^0.8.2",
"lodash": "^4.17.21",
"next": "16.1.7",
"next-themes": "^0.2.1",
Expand Down
Loading
Loading