mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: migration errors by testing on a production instance.
This commit is contained in:
+1
-1
@@ -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.",
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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}),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user