Skip to content

Commit a7acab4

Browse files
fomalhautbN2D4ellipsis-dev[bot]
authored
Auto migration (#526)
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Introduces an automated database migration system, replacing manual Prisma commands with new scripts and updating workflows, configurations, and tests accordingly. > > - **Auto-Migration System**: > - Introduces `db-migrations.ts` script for handling database migrations automatically. > - Adds utility functions in `utils.tsx` for managing migration files. > - Implements `applyMigrations` and `runMigrationNeeded` in `index.tsx` for executing migrations. > - **Workflow and Scripts**: > - Updates GitHub workflows (`check-prisma-migrations.yaml`, `e2e-api-tests.yaml`) to use new migration commands. > - Replaces `prisma migrate` commands with `db:init`, `db:migrate`, etc., in `package.json` and `README.md`. > - **Testing**: > - Adds `auto-migration.tests.ts` for testing migration logic and concurrency handling. > - **Configuration**: > - Updates `.env.development` and `vitest.config.ts` for new environment variables and paths. > - Modifies `turbo.json` and `package.json` to include new migration tasks and scripts. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup> for 2c24183. You can [customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN --> --------- Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com> Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent 9f9a103 commit a7acab4

71 files changed

Lines changed: 1097 additions & 199 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.

.github/workflows/check-prisma-migrations.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ jobs:
3939
run: docker run -d --name postgres-prisma-diff-shadow -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=PLACEHOLDER-PASSWORD--dfaBC1hm1v -e POSTGRES_DB=postgres -p 5432:5432 postgres:latest
4040

4141
- name: Check for differences in Prisma schema and migrations
42-
run: pnpm run prisma migrate diff --from-migrations ./prisma/migrations --to-schema-datamodel ./prisma/schema.prisma --shadow-database-url postgres://postgres:PLACEHOLDER-PASSWORD--dfaBC1hm1v@localhost:5432/postgres --exit-code
42+
run: cd apps/backend && pnpm run prisma migrate diff --from-migrations ./prisma/migrations --to-schema-datamodel ./prisma/schema.prisma --shadow-database-url postgres://postgres:PLACEHOLDER-PASSWORD--dfaBC1hm1v@localhost:5432/postgres --exit-code

.github/workflows/e2e-api-tests.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
env:
1818
NODE_ENV: test
1919
STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes
20+
STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe"
2021

2122
strategy:
2223
matrix:
@@ -96,7 +97,7 @@ jobs:
9697
run: npx wait-on tcp:localhost:8113
9798

9899
- name: Initialize database
99-
run: pnpm run prisma -- migrate reset --force
100+
run: pnpm run db:init
100101

101102
- name: Start stack-backend in background
102103
uses: JarvusInnovations/background-action@v1.0.7

.github/workflows/e2e-source-of-truth-api-tests.yaml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes
2020
STACK_OVERRIDE_SOURCE_OF_TRUTH: '{"type": "postgres", "connectionString": "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/source-of-truth-db?schema=sot-schema"}'
2121
STACK_TEST_SOURCE_OF_TRUTH: true
22+
STACK_DIRECT_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/stackframe"
2223

2324
strategy:
2425
matrix:
@@ -97,11 +98,14 @@ jobs:
9798
- name: Wait on Svix
9899
run: npx wait-on tcp:localhost:8113
99100

100-
- name: Initialize source of truth database
101-
run: "STACK_DIRECT_DATABASE_CONNECTION_STRING='postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/source-of-truth-db?schema=sot-schema' pnpm run prisma -- migrate reset --force --skip-seed"
101+
- name: Create source-of-truth database and schema
102+
run: |
103+
psql postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/postgres -c "CREATE DATABASE \"source-of-truth-db\";"
104+
psql postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:5432/source-of-truth-db -c "CREATE SCHEMA \"sot-schema\";"
102105
103106
- name: Initialize database
104-
run: pnpm run prisma -- migrate reset --force
107+
run: pnpm run db:init
108+
105109
- name: Start stack-backend in background
106110
uses: JarvusInnovations/background-action@v1.0.7
107111
with:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,10 @@ pnpm run prisma studio
163163
164164
### Database migrations
165165
166-
If you make changes to the Prisma schema, you need to run the following command to create a migration:
166+
If you make changes to the Prisma schema, you need to run the following command to create a migration file:
167167
168168
```sh
169-
pnpm run prisma migrate dev
169+
pnpm run db:migration-gen
170170
```
171171
172172
### Chat with the codebase

apps/backend/package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@
1717
"codegen-prisma:watch": "pnpm run prisma generate --watch",
1818
"codegen-route-info": "pnpm run with-env tsx scripts/generate-route-info.ts",
1919
"codegen-route-info:watch": "pnpm run with-env tsx watch --clear-screen=false scripts/generate-route-info.ts",
20-
"codegen": "pnpm run codegen-prisma && pnpm run codegen-route-info",
20+
"codegen": "pnpm run with-env pnpm run generate-migration-imports && pnpm run with-env bash -c 'if [ \"$STACK_ACCELERATE_ENABLED\" = \"true\" ]; then pnpm run prisma generate --no-engine && pnpm run generate-openapi; else pnpm run codegen-prisma && pnpm run generate-openapi; fi' && pnpm run codegen-route-info",
2121
"codegen:watch": "concurrently -n \"prisma,docs,route-info\" -k \"pnpm run codegen-prisma:watch\" \"pnpm run watch-docs\" \"pnpm run codegen-route-info:watch\"",
2222
"psql-inner": "psql $STACK_DATABASE_CONNECTION_STRING",
2323
"psql": "pnpm run with-env pnpm run psql-inner",
24-
"prisma": "pnpm run with-env prisma",
2524
"prisma-studio": "pnpm run with-env prisma studio --port 8106 --browser none",
25+
"prisma": "pnpm run with-env prisma",
26+
"db:migration-gen": "pnpm run with-env tsx scripts/db-migrations.ts generate-migration-file",
27+
"db:reset": "pnpm run with-env tsx scripts/db-migrations.ts reset",
28+
"db:seed": "pnpm run with-env tsx scripts/db-migrations.ts seed",
29+
"db:init": "pnpm run with-env tsx scripts/db-migrations.ts init",
30+
"db:migrate": "pnpm run with-env tsx scripts/db-migrations.ts migrate",
31+
"generate-migration-imports": "pnpm run with-env tsx scripts/generate-migration-imports.ts",
32+
"generate-migration-imports:watch": "chokidar 'prisma/migrations/**/*.sql' -c 'pnpm run generate-migration-imports'",
2633
"lint": "next lint",
2734
"watch-docs": "pnpm run with-env bash -c 'tsx watch --clear-screen=false scripts/generate-openapi-fumadocs.ts && pnpm run --filter=@stackframe/stack-docs generate-openapi-docs'",
2835
"generate-openapi": "pnpm run with-env tsx scripts/generate-openapi.ts",
@@ -62,6 +69,8 @@
6269
"@vercel/otel": "^1.10.4",
6370
"ai": "^4.3.17",
6471
"bcrypt": "^5.1.1",
72+
"chokidar-cli": "^3.0.0",
73+
"dotenv": "^16.4.5",
6574
"dotenv-cli": "^7.3.0",
6675
"freestyle-sandboxes": "^0.0.92",
6776
"jose": "^5.2.2",
@@ -70,13 +79,15 @@
7079
"nodemailer": "^6.9.10",
7180
"oidc-provider": "^8.5.1",
7281
"openid-client": "5.6.4",
82+
"postgres": "^3.4.5",
7383
"pg": "^8.16.3",
7484
"posthog-node": "^4.1.0",
7585
"react": "19.0.0",
7686
"react-dom": "19.0.0",
7787
"semver": "^7.6.3",
7888
"sharp": "^0.32.6",
7989
"svix": "^1.25.0",
90+
"vite": "^6.1.0",
8091
"yaml": "^2.4.5",
8192
"yup": "^1.4.0",
8293
"zod": "^3.23.8"

apps/backend/prisma/migrations/20240910211533_remove_shared_facebook/migration.sql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,9 @@ DELETE FROM "ProxiedOAuthProviderConfig"
3737
WHERE "type" = 'FACEBOOK';
3838

3939
-- AlterEnum
40-
BEGIN;
40+
-- SPLIT_STATEMENT_SENTINEL
4141
CREATE TYPE "ProxiedOAuthProviderType_new" AS ENUM ('GITHUB', 'GOOGLE', 'MICROSOFT', 'SPOTIFY');
4242
ALTER TABLE "ProxiedOAuthProviderConfig" ALTER COLUMN "type" TYPE "ProxiedOAuthProviderType_new" USING ("type"::text::"ProxiedOAuthProviderType_new");
4343
ALTER TYPE "ProxiedOAuthProviderType" RENAME TO "ProxiedOAuthProviderType_old";
4444
ALTER TYPE "ProxiedOAuthProviderType_new" RENAME TO "ProxiedOAuthProviderType";
4545
DROP TYPE "ProxiedOAuthProviderType_old";
46-
COMMIT;

apps/backend/prisma/migrations/20250304200822_add_project_user_count/migration.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ UPDATE "Project" SET "userCount" = (
88
);
99

1010
-- Create function to update userCount
11+
-- SPLIT_STATEMENT_SENTINEL
12+
-- SINGLE_STATEMENT_SENTINEL
1113
CREATE OR REPLACE FUNCTION update_project_user_count()
1214
RETURNS TRIGGER AS $$
1315
BEGIN
@@ -30,6 +32,7 @@ BEGIN
3032
RETURN NULL;
3133
END;
3234
$$ LANGUAGE plpgsql;
35+
-- SPLIT_STATEMENT_SENTINEL
3336

3437
-- Create triggers
3538
DROP TRIGGER IF EXISTS project_user_insert_trigger ON "ProjectUser";

apps/backend/prisma/migrations/20250325235813_project_user_permissions/migration.sql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
66
*/
77
-- AlterEnum
8-
BEGIN;
98
CREATE TYPE "PermissionScope_new" AS ENUM ('PROJECT', 'TEAM');
109
ALTER TABLE "Permission" ALTER COLUMN "scope" TYPE "PermissionScope_new" USING ("scope"::text::"PermissionScope_new");
1110
ALTER TYPE "PermissionScope" RENAME TO "PermissionScope_old";
1211
ALTER TYPE "PermissionScope_new" RENAME TO "PermissionScope";
1312
DROP TYPE "PermissionScope_old";
14-
COMMIT;
13+
-- SPLIT_STATEMENT_SENTINEL
1514

1615
-- AlterTable
1716
ALTER TABLE "Permission" ADD COLUMN "isDefaultProjectPermission" BOOLEAN NOT NULL DEFAULT false;

apps/backend/prisma/migrations/20250425171311_remove_old_config/migration.sql

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,14 @@ ALTER TABLE "AuthMethod" DROP COLUMN "authMethodConfigId",
167167
DROP COLUMN "projectConfigId";
168168

169169
-- AlterTable
170-
BEGIN;
171170
ALTER TABLE "ConnectedAccount" ADD COLUMN "configOAuthProviderId" TEXT;
172171
UPDATE "ConnectedAccount" SET "configOAuthProviderId" = "oauthProviderConfigId";
173172
ALTER TABLE "ConnectedAccount" ALTER COLUMN "configOAuthProviderId" SET NOT NULL;
174173
ALTER TABLE "ConnectedAccount" DROP COLUMN "oauthProviderConfigId";
175174
ALTER TABLE "ConnectedAccount" DROP COLUMN "connectedAccountConfigId";
176175
ALTER TABLE "ConnectedAccount" DROP COLUMN "projectConfigId";
177-
COMMIT;
178176

179177
-- AlterTable
180-
BEGIN;
181178
ALTER TABLE "EmailTemplate" DROP CONSTRAINT "EmailTemplate_pkey";
182179
ALTER TABLE "EmailTemplate" ADD COLUMN "projectId" TEXT;
183180

@@ -189,12 +186,15 @@ JOIN "Project" P ON P."configId" = PC."id"
189186
WHERE ET."projectConfigId" = PC."id";
190187

191188
-- Check if we have any null projectId values
189+
-- SPLIT_STATEMENT_SENTINEL
190+
-- SINGLE_STATEMENT_SENTINEL
192191
DO $$
193192
BEGIN
194193
IF EXISTS (SELECT 1 FROM "EmailTemplate" WHERE "projectId" IS NULL) THEN
195194
RAISE EXCEPTION 'Some EmailTemplate records have null projectId values after migration';
196195
END IF;
197196
END $$;
197+
-- SPLIT_STATEMENT_SENTINEL
198198

199199
-- Now make the column NOT NULL
200200
ALTER TABLE "EmailTemplate" ALTER COLUMN "projectId" SET NOT NULL;
@@ -204,38 +204,30 @@ ALTER TABLE "EmailTemplate" ADD CONSTRAINT "EmailTemplate_pkey" PRIMARY KEY ("pr
204204

205205
-- Drop the old column
206206
ALTER TABLE "EmailTemplate" DROP COLUMN "projectConfigId";
207-
COMMIT;
208207

209208
-- AlterTable
210-
BEGIN;
211209
ALTER TABLE "OAuthAccessToken" ADD COLUMN "configOAuthProviderId" TEXT;
212210
UPDATE "OAuthAccessToken" SET "configOAuthProviderId" = "oAuthProviderConfigId";
213211
ALTER TABLE "OAuthAccessToken" ALTER COLUMN "configOAuthProviderId" SET NOT NULL;
214212
ALTER TABLE "OAuthAccessToken" DROP COLUMN "oAuthProviderConfigId";
215-
COMMIT;
216213

217214
-- AlterTable
218-
BEGIN;
219215
ALTER TABLE "OAuthAuthMethod" ADD COLUMN "configOAuthProviderId" TEXT;
220216
UPDATE "OAuthAuthMethod" SET "configOAuthProviderId" = "oauthProviderConfigId";
221217
ALTER TABLE "OAuthAuthMethod" ALTER COLUMN "configOAuthProviderId" SET NOT NULL;
222218
ALTER TABLE "OAuthAuthMethod" DROP COLUMN "oauthProviderConfigId";
223219
ALTER TABLE "OAuthAuthMethod" DROP COLUMN "projectConfigId";
224-
COMMIT;
225220

226221
-- AlterTable
227-
BEGIN;
228222
ALTER TABLE "OAuthToken" ADD COLUMN "configOAuthProviderId" TEXT;
229223
UPDATE "OAuthToken" SET "configOAuthProviderId" = "oAuthProviderConfigId";
230224
ALTER TABLE "OAuthToken" ALTER COLUMN "configOAuthProviderId" SET NOT NULL;
231225
ALTER TABLE "OAuthToken" DROP COLUMN "oAuthProviderConfigId";
232-
COMMIT;
233226

234227
-- AlterTable
235228
ALTER TABLE "Project" DROP COLUMN "configId";
236229

237230
-- AlterTable
238-
BEGIN;
239231
ALTER TABLE "ProjectUserDirectPermission" ADD COLUMN "permissionId" TEXT;
240232

241233
-- Update permissionId with values from Permission table
@@ -249,24 +241,22 @@ ALTER TABLE "ProjectUserDirectPermission" ALTER COLUMN "permissionId" SET NOT NU
249241

250242
-- Drop the old column
251243
ALTER TABLE "ProjectUserDirectPermission" DROP COLUMN "permissionDbId";
252-
COMMIT;
253244

254245
-- AlterTable
255-
BEGIN;
256246
ALTER TABLE "ProjectUserOAuthAccount" ADD COLUMN "configOAuthProviderId" TEXT;
257247
UPDATE "ProjectUserOAuthAccount" SET "configOAuthProviderId" = "oauthProviderConfigId";
258248
ALTER TABLE "ProjectUserOAuthAccount" ALTER COLUMN "configOAuthProviderId" SET NOT NULL;
259249
ALTER TABLE "ProjectUserOAuthAccount" DROP CONSTRAINT "ProjectUserOAuthAccount_pkey";
260250
ALTER TABLE "ProjectUserOAuthAccount" DROP COLUMN "oauthProviderConfigId";
261251
ALTER TABLE "ProjectUserOAuthAccount" DROP COLUMN "projectConfigId";
262252
ALTER TABLE "ProjectUserOAuthAccount" ADD CONSTRAINT "ProjectUserOAuthAccount_pkey" PRIMARY KEY ("tenancyId", "configOAuthProviderId", "providerAccountId");
263-
COMMIT;
264253

265254
-- AlterTable
266-
BEGIN;
267255
ALTER TABLE "TeamMemberDirectPermission" ADD COLUMN "permissionId" TEXT;
268256

269257
-- Check for rows where both or neither field is populated
258+
-- SPLIT_STATEMENT_SENTINEL
259+
-- SINGLE_STATEMENT_SENTINEL
270260
DO $$
271261
BEGIN
272262
IF EXISTS (
@@ -277,6 +267,7 @@ BEGIN
277267
RAISE EXCEPTION 'Invalid state: Each TeamMemberDirectPermission must have exactly one of permissionDbId or systemPermission set';
278268
END IF;
279269
END $$;
270+
-- SPLIT_STATEMENT_SENTINEL
280271

281272
-- Update permissionId using systemPermission when available
282273
UPDATE "TeamMemberDirectPermission"
@@ -295,7 +286,6 @@ ALTER TABLE "TeamMemberDirectPermission" ALTER COLUMN "permissionId" SET NOT NUL
295286
-- Then drop the old columns
296287
ALTER TABLE "TeamMemberDirectPermission" DROP COLUMN "permissionDbId";
297288
ALTER TABLE "TeamMemberDirectPermission" DROP COLUMN "systemPermission";
298-
COMMIT;
299289

300290
-- DropTable
301291
DROP TABLE "AuthMethodConfig";

apps/backend/prisma/seed.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async function seed() {
5858
}
5959

6060
const internalTenancy = await getSoleTenancyFromProjectBranch("internal", DEFAULT_BRANCH_ID);
61-
const internalPrisma = getPrismaClientForTenancy(internalTenancy);
61+
const internalPrisma = await getPrismaClientForTenancy(internalTenancy);
6262

6363
internalProject = await createOrUpdateProject({
6464
projectId: 'internal',
@@ -142,7 +142,7 @@ async function seed() {
142142
}
143143

144144
if (adminGithubId) {
145-
const githubAccount = await getPrismaClientForTenancy(internalTenancy).projectUserOAuthAccount.findFirst({
145+
const githubAccount = await internalPrisma.projectUserOAuthAccount.findFirst({
146146
where: {
147147
tenancyId: internalTenancy.id,
148148
configOAuthProviderId: 'github',
@@ -153,7 +153,7 @@ async function seed() {
153153
if (githubAccount) {
154154
console.log(`GitHub account already exists, skipping creation`);
155155
} else {
156-
await getPrismaClientForTenancy(internalTenancy).projectUserOAuthAccount.create({
156+
await internalPrisma.projectUserOAuthAccount.create({
157157
data: {
158158
tenancyId: internalTenancy.id,
159159
projectUserId: newUser.projectUserId,

0 commit comments

Comments
 (0)