fix: migration errors by testing on a production instance.

This commit is contained in:
charlesgauthereau
2026-01-18 14:10:35 +01:00
parent 3d695709d5
commit 8ee44ffea1
13 changed files with 2417 additions and 93 deletions
+1
View File
@@ -53,6 +53,7 @@ export const db = drizzle({
client: pool,
// logger: process.env.NODE_ENV != 'production',
schema: schemas,
});
export async function makeMigration() {
@@ -1,2 +0,0 @@
ALTER TABLE "restorations" ADD COLUMN "backup_storage_id" uuid;--> statement-breakpoint
ALTER TABLE "restorations" ADD CONSTRAINT "restorations_backup_storage_id_backup_storage_id_fk" FOREIGN KEY ("backup_storage_id") REFERENCES "public"."backup_storage"("id") ON DELETE cascade ON UPDATE no action;
+82 -70
View File
@@ -1,51 +1,66 @@
-- Custom SQL migration file, put your code below! --
-- Custom SQL Migration: Create Storage Channel and Populate Backup Storage
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Add column
ALTER TABLE public.restorations
ADD COLUMN backup_storage_id uuid;
-- Add foreign key
ALTER TABLE public.restorations
ADD CONSTRAINT restorations_backup_storage_id_fk
FOREIGN KEY (backup_storage_id)
REFERENCES public.backup_storage(id)
ON DELETE CASCADE
ON UPDATE NO ACTION;
DO $$
DECLARE
s RECORD;
channel_id UUID;
existing_local RECORD;
p RECORD;
d RECORD;
b RECORD;
BEGIN
-- Get the current settings (assume single row)
-- Get current settings (assume single row)
SELECT * INTO s FROM settings LIMIT 1;
-- Create S3 or Local storage channel
IF s.storage = 's3' THEN
-- Create a new S3 storage_channel
INSERT INTO storage_channel (
id, organization_id, provider, name, config, enabled, created_at, updated_at
)
VALUES (
gen_random_uuid(),
NULL,
's3',
'Default S3 Channel',
jsonb_build_object(
'endPointUrl', s.s3_endpoint_url,
'accessKey', s.s3_access_key_id,
'secretKey', s.s3_secret_access_key,
'bucketName', s.s3_bucket_name
),
true,
NOW(),
NOW()
)
RETURNING id INTO channel_id;
BEGIN
INSERT INTO storage_channel (
id, organization_id, provider, name, config, enabled, created_at, updated_at
)
VALUES (
gen_random_uuid(),
NULL,
's3',
'Default S3 Channel',
jsonb_build_object(
'endPointUrl', s.s3_endpoint_url,
'accessKey', s.s3_access_key_id,
'secretKey', s.s3_secret_access_key,
'bucketName', s.s3_bucket_name
),
true,
NOW(),
NOW()
)
RETURNING id INTO channel_id;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Failed to create S3 storage_channel: %', SQLERRM;
-- Try to fallback to existing S3 channel
SELECT id INTO channel_id FROM storage_channel WHERE provider = 's3' LIMIT 1;
END;
ELSE
-- local storage: check if a local channel exists
SELECT * INTO existing_local
FROM storage_channel
WHERE provider = 'local'
LIMIT 1;
IF existing_local IS NULL THEN
-- Create local channel
INSERT INTO storage_channel (
id, organization_id, provider, name, config, enabled, created_at, updated_at
)
@@ -61,7 +76,6 @@ DO $$
)
RETURNING id INTO channel_id;
ELSE
-- Update existing local channel
UPDATE storage_channel
SET name = 'System',
config = '{}'::jsonb,
@@ -72,55 +86,53 @@ DO $$
END IF;
END IF;
-- **Update settings.default_storage_channel_id for BOTH s3 and local**
-- Safety check
IF channel_id IS NULL THEN
RAISE EXCEPTION 'channel_id is NULL. Cannot proceed with backup_storage inserts.';
END IF;
-- Update settings with default storage channel
UPDATE settings
SET default_storage_channel_id = channel_id
WHERE id = s.id;
-- Loop over projects -> databases -> backups
FOR p IN SELECT id, slug FROM projects LOOP
RAISE NOTICE 'Processing project: %', p.slug;
/*
* Loop order:
* project -> database -> backup
*/
FOR p IN
SELECT id, slug
FROM projects
LOOP
FOR d IN
SELECT id
FROM databases
WHERE project_id = p.id
LOOP
FOR b IN
SELECT *
FROM backups
WHERE database_id = d.id
LOOP
INSERT INTO backup_storage (
id,
backup_id,
storage_channel_id,
status,
path,
size,
checksum,
created_at,
updated_at
)
VALUES (
gen_random_uuid(),
b.id,
channel_id,
'success',
format('backups/%s/%s', p.slug, b.file),
b.file_size,
NULL,
NOW(),
NOW()
);
FOR d IN SELECT id FROM databases WHERE project_id = p.id LOOP
RAISE NOTICE ' Database ID: %', d.id;
FOR b IN SELECT id, file AS file_name, file_size FROM backups WHERE database_id = d.id LOOP
RAISE NOTICE ' Backup: %', b.file_name;
BEGIN
INSERT INTO backup_storage (
id,
backup_id,
storage_channel_id,
status,
path,
size,
checksum,
created_at,
updated_at
)
VALUES (
gen_random_uuid(),
b.id,
channel_id,
'success',
format('backups/%s/%s', p.slug, b.file_name),
b.file_size,
NULL,
NOW(),
NOW()
);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Failed to insert backup_storage for backup %: %', b.id, SQLERRM;
END;
END LOOP;
END LOOP;
END LOOP;
END $$;
@@ -0,0 +1,5 @@
ALTER TABLE "settings" DROP COLUMN "storage";--> statement-breakpoint
ALTER TABLE "settings" DROP COLUMN "s3_endpoint_url";--> statement-breakpoint
ALTER TABLE "settings" DROP COLUMN "s3_access_key_id";--> statement-breakpoint
ALTER TABLE "settings" DROP COLUMN "s3_secret_access_key";--> statement-breakpoint
ALTER TABLE "settings" DROP COLUMN "s3_bucket_name";
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -188,7 +188,7 @@
"idx": 26,
"version": "7",
"when": 1768665081232,
"tag": "0026_demonic_santa_claus",
"tag": "0026_storage-backend",
"breakpoints": true
},
{
@@ -197,6 +197,13 @@
"when": 1768676733859,
"tag": "0027_special_the_santerians",
"breakpoints": true
},
{
"idx": 28,
"version": "7",
"when": 1768733221276,
"tag": "0028_graceful_ben_parker",
"breakpoints": true
}
]
}
-5
View File
@@ -13,12 +13,7 @@ import {backup, database, restoration, retentionPolicy} from "@/db/schema/07_dat
export const setting = pgTable("settings", {
id: uuid("id").primaryKey().defaultRandom(),
storage: typeStorageEnum("storage").default("local").notNull(),
name: varchar("name", {length: 255}).unique().notNull(),
s3EndPointUrl: varchar("s3_endpoint_url", {length: 255}),
s3AccessKeyId: varchar("s3_access_key_id", {length: 255}),
s3SecretAccessKey: varchar("s3_secret_access_key", {length: 255}),
S3BucketName: varchar("s3_bucket_name", {length: 255}),
smtpPassword: varchar("smtp_password", {length: 255}),
smtpFrom: varchar("smtp_from", {length: 255}),
smtpHost: varchar("smtp_host", {length: 255}),
+1
View File
@@ -65,6 +65,7 @@ export const retentionPolicy = pgTable("retention_policies", {
export const restoration = pgTable("restorations", {
id: uuid("id").primaryKey().defaultRandom(),
status: statusEnum("status").default("waiting").notNull(),
backupStorageId: uuid("backup_storage_id")
.references(() => backupStorage.id, {onDelete: "cascade"}),
backupId: uuid("backup_id")