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 -1
View File
@@ -2,7 +2,7 @@ name: portabase-dev
services:
db:
image: postgres:16-alpine
image: postgres:17-alpine
ports:
- "5433:5432"
volumes:
@@ -93,7 +93,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
toast.success("Backup deleted successfully.")
closeModal()
} else {
toast.error(inner?.actionError?.message);
toast.error(inner?.actionError?.message ?? "An error occurred.");
}
}
},
@@ -53,10 +53,12 @@ export const downloadBackupAction = userAction.schema(
},
};
const result = await dispatchStorage(input, undefined, backupStorage.storageChannelId);
console.log(input)
const result = await dispatchStorage(input, undefined, backupStorage.storageChannelId);
console.log(result);
return {
success: true,
success: result.success,
value: result.url,
actionSuccess: {
message: "Backup Storage downloaded successfully.",
+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")
+2 -2
View File
@@ -20,7 +20,8 @@ async function getS3Client(config: S3Config) {
});
}
const BASE_DIR = "/";
// Keep it like that
const BASE_DIR = "";
async function ensureBucket(config: S3Config) {
@@ -56,7 +57,6 @@ export async function getS3(config: S3Config, input: { data: StorageGetInput }):
// const key = input.data.path;
const key = `${BASE_DIR}${input.data.path}`;
try {
await client.statObject(config.bucketName, key);
} catch {
-9
View File
@@ -29,16 +29,11 @@ async function setupCronJobs() {
async function createSettingsIfNotExist() {
const configSettings = {
name: "system",
// storage: env.STORAGE_TYPE!,
smtpPassword: env.SMTP_PASSWORD ?? null,
smtpFrom: env.SMTP_FROM ?? null,
smtpHost: env.SMTP_HOST ?? null,
smtpPort: env.SMTP_PORT ?? null,
smtpUser: env.SMTP_USER ?? null,
// s3EndPointUrl: env.S3_ENDPOINT ?? null,
// s3AccessKeyId: env.S3_ACCESS_KEY ?? null,
// s3SecretAccessKey: env.S3_SECRET_KEY ?? null,
// S3BucketName: env.S3_BUCKET_NAME ?? null,
};
const [existing] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
@@ -75,10 +70,6 @@ async function createSettingsIfNotExist() {
await db.update(drizzleDb.schemas.storageChannel).set(localChannelValues).where(eq(drizzleDb.schemas.storageChannel.provider, "local"));
}
}
async function createDefaultOrganization() {