Skip to content

Commit 1412d86

Browse files
authored
chore: make migrations idempotent (#5476)
## Description 1. What is this PR about (link the issue and add a short description) ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent 2f393f5 commit 1412d86

File tree

6 files changed

+68
-50
lines changed

6 files changed

+68
-50
lines changed

.github/workflows/migrate.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ jobs:
5757
env:
5858
DIRECT_URL: ${{ secrets.DIRECT_URL }}
5959

60-
# Execute db tests (@todo: can be done only after applying the migrations, now always())
60+
# Execute db tests (runs after migrations, even if they fail)
6161
db-tests:
62+
# Always run tests to see failures on CI, but only after migrate job completes
6263
if: always()
6364

6465
needs: [migrate]
@@ -75,7 +76,7 @@ jobs:
7576

7677
- uses: pnpm/action-setup@v4
7778

78-
- name: pnpm instal
79+
- name: Run database tests
7980
run: pnpm -r db-test
8081
env:
8182
DIRECT_URL: ${{ secrets.DIRECT_URL }}

.prettierignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
pnpm-lock.yaml
22
packages/prisma-client/prisma/migrations/*/client
3-
packages/prisma-client/**/*.d.ts
4-
packages/prisma-client/prisma/migrations/**/*.sql
5-
packages/postgrest/supabase/tests/*.sql
3+
packages/prisma-client/**/*.d.ts

.vscode/settings.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
{
2-
"typescript.tsdk": "node_modules/typescript/lib"
2+
"typescript.tsdk": "node_modules/typescript/lib",
3+
// Disable formatting for SQL files
4+
"[sql]": {
5+
"editor.formatOnSave": false,
6+
"editor.formatOnPaste": false,
7+
"editor.formatOnType": false
8+
},
9+
// Disable Prettier for SQL files specifically
10+
"prettier.enable": true,
11+
"prettier.ignorePath": ".prettierignore",
12+
// Exclude SQL files from auto-formatting
13+
"files.associations": {
14+
"*.sql": "sql"
15+
}
316
}

apps/builder/app/dashboard/projects/project-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ export const ProjectCard = ({
222222
</>
223223
)}
224224
<br />
225-
{latestBuildVirtual?.publishStatus === "PUBLISHED" ? (
225+
{isPublished && latestBuildVirtual ? (
226226
<>Published: {formatDate(latestBuildVirtual.createdAt)}</>
227227
) : (
228228
<>Not published</>

packages/prisma-client/prisma/migrations/20251129093846_add_updated_at_to_latest_build_virtual/migration.sql

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
-- Add updatedAt field to latestBuildVirtual virtual table (type definition)
22
-- and update both functions to include Build's updatedAt timestamp
3+
4+
-- First, drop existing functions that depend on the old table structure
5+
DROP FUNCTION IF EXISTS "latestBuildVirtual"("Project");
6+
DROP FUNCTION IF EXISTS "latestBuildVirtual"("domainsVirtual");
7+
DROP FUNCTION IF EXISTS "latestProjectDomainBuildVirtual"("Project");
8+
DROP FUNCTION IF EXISTS "latestBuildVirtual"("DashboardProject");
9+
310
-- Add updatedAt column to the virtual table type definition
4-
ALTER TABLE
5-
"latestBuildVirtual"
6-
ADD
7-
COLUMN "updatedAt" timestamp(3) with time zone NOT NULL;
11+
ALTER TABLE "latestBuildVirtual"
12+
ADD COLUMN "updatedAt" timestamp(3) with time zone NOT NULL DEFAULT NOW();
813

9-
COMMENT ON COLUMN "latestBuildVirtual"."updatedAt" IS 'Timestamp indicating when the Build was last updated';
14+
COMMENT ON COLUMN "public"."latestBuildVirtual"."updatedAt" IS 'Timestamp indicating when the Build was last updated';
1015

1116
-- Recreate the function for Project with updatedAt field
1217
CREATE
@@ -18,7 +23,7 @@ SELECT
1823
-- Use CASE to determine which domain to select based on conditions
1924
CASE
2025
WHEN (b.deployment :: jsonb ->> 'projectDomain') = p.domain
21-
OR (b.deployment :: jsonb -> 'domains') @ > to_jsonb(array [p.domain]) THEN p.domain
26+
OR (b.deployment :: jsonb -> 'domains') @> to_jsonb(array [p.domain]) THEN p.domain
2227
ELSE d.domain
2328
END AS "domain",
2429
b."createdAt",
@@ -30,7 +35,7 @@ FROM
3035
LEFT JOIN "ProjectDomain" pd ON pd."projectId" = p.id
3136
LEFT JOIN "Domain" d ON d.id = pd."domainId"
3237
WHERE
33-
b."projectId" = $ 1.id
38+
b."projectId" = $1.id
3439
AND b.deployment IS NOT NULL -- 'destination' IS NULL for backward compatibility; 'destination' = 'saas' for non-static builds
3540
AND (
3641
(b.deployment :: jsonb ->> 'destination') IS NULL
@@ -39,8 +44,8 @@ WHERE
3944
AND (
4045
-- Check if 'projectDomain' matches p.domain
4146
(b.deployment :: jsonb ->> 'projectDomain') = p.domain -- Check if 'domains' contains p.domain or d.domain
42-
OR (b.deployment :: jsonb -> 'domains') @ > to_jsonb(array [p.domain])
43-
OR (b.deployment :: jsonb -> 'domains') @ > to_jsonb(array [d.domain])
47+
OR (b.deployment :: jsonb -> 'domains') @> to_jsonb(array [p.domain])
48+
OR (b.deployment :: jsonb -> 'domains') @> to_jsonb(array [d.domain])
4449
)
4550
ORDER BY
4651
b."createdAt" DESC
@@ -65,11 +70,11 @@ SELECT
6570
b."updatedAt"
6671
FROM
6772
"Build" b
68-
JOIN "Domain" d ON d.id = $ 1."domainId"
73+
JOIN "Domain" d ON d.id = $1."domainId"
6974
WHERE
70-
b."projectId" = $ 1."projectId"
75+
b."projectId" = $1."projectId"
7176
AND b.deployment IS NOT NULL
72-
AND (b.deployment :: jsonb -> 'domains') @ > to_jsonb(array [d.domain])
77+
AND (b.deployment :: jsonb -> 'domains') @> to_jsonb(array [d.domain])
7378
ORDER BY
7479
b."createdAt" DESC
7580
LIMIT
@@ -96,15 +101,15 @@ FROM
96101
JOIN "Project" p ON b."projectId" = p.id
97102
LEFT JOIN "ProjectDomain" pd ON pd."projectId" = p.id
98103
WHERE
99-
b."projectId" = $ 1.id
104+
b."projectId" = $1.id
100105
AND b.deployment IS NOT NULL
101106
AND (
102107
(b.deployment :: jsonb ->> 'destination') IS NULL
103108
OR (b.deployment :: jsonb ->> 'destination') = 'saas'
104109
)
105110
AND (
106111
(b.deployment :: jsonb ->> 'projectDomain') = p.domain
107-
OR (b.deployment :: jsonb -> 'domains') @ > to_jsonb(array [p.domain])
112+
OR (b.deployment :: jsonb -> 'domains') @> to_jsonb(array [p.domain])
108113
)
109114
ORDER BY
110115
b."createdAt" DESC
@@ -113,4 +118,33 @@ LIMIT
113118

114119
$$ STABLE LANGUAGE sql;
115120

116-
COMMENT ON FUNCTION "latestProjectDomainBuildVirtual"("Project") IS 'This function computes the latest build for a project domain, ensuring it is a production (non-static) build, where the domain matches either the Project.domain field or exists in the related Domain table. It provides backward compatibility for older records with a missing "destination" field.';
121+
COMMENT ON FUNCTION "latestProjectDomainBuildVirtual"("Project") IS 'This function computes the latest build for a project domain, ensuring it is a production (non-static) build, where the domain matches either the Project.domain field or exists in the related Domain table. It provides backward compatibility for older records with a missing "destination" field.';
122+
123+
-- Add latestBuildVirtual function overload for DashboardProject view
124+
-- This is needed because PostgREST computed fields require a function
125+
-- that matches the source table/view type. DashboardProject is a view
126+
-- over Project, so we need this wrapper function that casts to Project type.
127+
CREATE
128+
OR REPLACE FUNCTION "latestBuildVirtual"("DashboardProject") RETURNS SETOF "latestBuildVirtual" ROWS 1 AS $$
129+
SELECT
130+
*
131+
FROM
132+
"latestBuildVirtual"(ROW($1.id, $1.title, $1.domain, $1."userId", $1."isDeleted", $1."createdAt", $1."previewImageAssetId", $1."marketplaceApprovalStatus", $1.tags)::"Project");
133+
134+
$$ STABLE LANGUAGE sql;
135+
136+
COMMENT ON FUNCTION "latestBuildVirtual"("DashboardProject") IS 'Wrapper function to make latestBuildVirtual work with DashboardProject view for PostgREST computed fields.';
137+
138+
-- Grant execute permissions to all PostgREST roles
139+
-- Uses DO block to check if roles exist before granting (prevents errors if roles are missing)
140+
DO $$
141+
DECLARE
142+
role_name TEXT;
143+
BEGIN
144+
FOREACH role_name IN ARRAY ARRAY['anon', 'authenticated', 'service_role']
145+
LOOP
146+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = role_name) THEN
147+
EXECUTE format('GRANT EXECUTE ON FUNCTION "latestBuildVirtual"("DashboardProject") TO %I', role_name);
148+
END IF;
149+
END LOOP;
150+
END $$;

packages/prisma-client/prisma/migrations/20251130131728_add_latest_build_virtual_for_dashboard_project/migration.sql

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)