From c1c8138197d93eaf24adfff62e5c2d56218fd7d8 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 14 Feb 2026 00:04:06 +0100 Subject: [PATCH] refactor: local repo path concat as a code migration --- .../migration.sql | 23 - .../snapshot.json | 2154 ----------------- app/server/modules/lifecycle/migrations.ts | 3 +- .../migrations/00004-concat-path-name.ts | 97 + 4 files changed, 99 insertions(+), 2178 deletions(-) delete mode 100644 app/drizzle/20260212184359_concat-path-name/migration.sql delete mode 100644 app/drizzle/20260212184359_concat-path-name/snapshot.json create mode 100644 app/server/modules/lifecycle/migrations/00004-concat-path-name.ts diff --git a/app/drizzle/20260212184359_concat-path-name/migration.sql b/app/drizzle/20260212184359_concat-path-name/migration.sql deleted file mode 100644 index 2227caf7..00000000 --- a/app/drizzle/20260212184359_concat-path-name/migration.sql +++ /dev/null @@ -1,23 +0,0 @@ -UPDATE `repositories_table` -SET - `config` = json_set( - `config`, - '$.path', - CASE - WHEN json_extract(`config`, '$.path') IS NULL OR trim(json_extract(`config`, '$.path')) = '' - THEN '/var/lib/zerobyte/repositories/' || json_extract(`config`, '$.name') - ELSE rtrim(json_extract(`config`, '$.path'), '/') || '/' || json_extract(`config`, '$.name') - END - ), - `updated_at` = (unixepoch() * 1000) -WHERE `type` = 'local' - AND json_extract(`config`, '$.name') IS NOT NULL - AND trim(json_extract(`config`, '$.name')) <> '' - AND ( - json_extract(`config`, '$.path') IS NULL - OR trim(json_extract(`config`, '$.path')) = '' - OR substr( - rtrim(json_extract(`config`, '$.path'), '/'), - -length('/' || json_extract(`config`, '$.name')) - ) != '/' || json_extract(`config`, '$.name') - ); diff --git a/app/drizzle/20260212184359_concat-path-name/snapshot.json b/app/drizzle/20260212184359_concat-path-name/snapshot.json deleted file mode 100644 index 0db364c3..00000000 --- a/app/drizzle/20260212184359_concat-path-name/snapshot.json +++ /dev/null @@ -1,2154 +0,0 @@ -{ - "id": "5201fdb9-eceb-4cc5-9ebe-bf1e2bd9b6dd", - "prevIds": [ - "3a308c54-d950-464f-9490-fee06985fbeb" - ], - "version": "7", - "dialect": "sqlite", - "ddl": [ - { - "name": "account", - "entityType": "tables" - }, - { - "name": "app_metadata", - "entityType": "tables" - }, - { - "name": "backup_schedule_mirrors_table", - "entityType": "tables" - }, - { - "name": "backup_schedule_notifications_table", - "entityType": "tables" - }, - { - "name": "backup_schedules_table", - "entityType": "tables" - }, - { - "name": "invitation", - "entityType": "tables" - }, - { - "name": "member", - "entityType": "tables" - }, - { - "name": "notification_destinations_table", - "entityType": "tables" - }, - { - "name": "organization", - "entityType": "tables" - }, - { - "name": "repositories_table", - "entityType": "tables" - }, - { - "name": "sessions_table", - "entityType": "tables" - }, - { - "name": "two_factor", - "entityType": "tables" - }, - { - "name": "users_table", - "entityType": "tables" - }, - { - "name": "verification", - "entityType": "tables" - }, - { - "name": "volumes_table", - "entityType": "tables" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "account_id", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "provider_id", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "user_id", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "access_token", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "refresh_token", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id_token", - "entityType": "columns", - "table": "account" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "access_token_expires_at", - "entityType": "columns", - "table": "account" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "refresh_token_expires_at", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "scope", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "password", - "entityType": "columns", - "table": "account" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "account" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "account" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "key", - "entityType": "columns", - "table": "app_metadata" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "value", - "entityType": "columns", - "table": "app_metadata" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "app_metadata" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "app_metadata" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": true, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "schedule_id", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "repository_id", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "true", - "generated": null, - "name": "enabled", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_copy_at", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_copy_status", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_copy_error", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "backup_schedule_mirrors_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "schedule_id", - "entityType": "columns", - "table": "backup_schedule_notifications_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "destination_id", - "entityType": "columns", - "table": "backup_schedule_notifications_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "notify_on_start", - "entityType": "columns", - "table": "backup_schedule_notifications_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "notify_on_success", - "entityType": "columns", - "table": "backup_schedule_notifications_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "true", - "generated": null, - "name": "notify_on_warning", - "entityType": "columns", - "table": "backup_schedule_notifications_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "true", - "generated": null, - "name": "notify_on_failure", - "entityType": "columns", - "table": "backup_schedule_notifications_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "backup_schedule_notifications_table" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": true, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "short_id", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "name", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "volume_id", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "repository_id", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "true", - "generated": null, - "name": "enabled", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "cron_expression", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "retention_policy", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": "'[]'", - "generated": null, - "name": "exclude_patterns", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": "'[]'", - "generated": null, - "name": "exclude_if_present", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": "'[]'", - "generated": null, - "name": "include_patterns", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_backup_at", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_backup_status", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_backup_error", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "next_backup_at", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "one_file_system", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "0", - "generated": null, - "name": "sort_order", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "organization_id", - "entityType": "columns", - "table": "backup_schedules_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "organization_id", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "email", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "role", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": "'pending'", - "generated": null, - "name": "status", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "expires_at", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "inviter_id", - "entityType": "columns", - "table": "invitation" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "member" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "organization_id", - "entityType": "columns", - "table": "member" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "user_id", - "entityType": "columns", - "table": "member" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": "'member'", - "generated": null, - "name": "role", - "entityType": "columns", - "table": "member" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "member" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": true, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "name", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "true", - "generated": null, - "name": "enabled", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "type", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "config", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "organization_id", - "entityType": "columns", - "table": "notification_destinations_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "organization" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "name", - "entityType": "columns", - "table": "organization" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "slug", - "entityType": "columns", - "table": "organization" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "logo", - "entityType": "columns", - "table": "organization" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "organization" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "metadata", - "entityType": "columns", - "table": "organization" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "short_id", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "name", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "type", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "config", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": "'auto'", - "generated": null, - "name": "compression_mode", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": "'unknown'", - "generated": null, - "name": "status", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_checked", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_error", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "doctor_result", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "upload_limit_enabled", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "real", - "notNull": true, - "autoincrement": false, - "default": "1", - "generated": null, - "name": "upload_limit_value", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": "'Mbps'", - "generated": null, - "name": "upload_limit_unit", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "download_limit_enabled", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "real", - "notNull": true, - "autoincrement": false, - "default": "1", - "generated": null, - "name": "download_limit_value", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": "'Mbps'", - "generated": null, - "name": "download_limit_unit", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "organization_id", - "entityType": "columns", - "table": "repositories_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "user_id", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "token", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "expires_at", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "ip_address", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "user_agent", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "impersonated_by", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "active_organization_id", - "entityType": "columns", - "table": "sessions_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "two_factor" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "secret", - "entityType": "columns", - "table": "two_factor" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "backup_codes", - "entityType": "columns", - "table": "two_factor" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "user_id", - "entityType": "columns", - "table": "two_factor" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "username", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "password_hash", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "has_downloaded_restic_password", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "name", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "email", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "email_verified", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "image", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "display_username", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "two_factor_enabled", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": "'user'", - "generated": null, - "name": "role", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "false", - "generated": null, - "name": "banned", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "ban_reason", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "ban_expires", - "entityType": "columns", - "table": "users_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "verification" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "identifier", - "entityType": "columns", - "table": "verification" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "value", - "entityType": "columns", - "table": "verification" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "expires_at", - "entityType": "columns", - "table": "verification" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "verification" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "verification" - }, - { - "type": "integer", - "notNull": false, - "autoincrement": true, - "default": null, - "generated": null, - "name": "id", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "short_id", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "name", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "type", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": "'unmounted'", - "generated": null, - "name": "status", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "text", - "notNull": false, - "autoincrement": false, - "default": null, - "generated": null, - "name": "last_error", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "last_health_check", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "created_at", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "(unixepoch() * 1000)", - "generated": null, - "name": "updated_at", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "config", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "integer", - "notNull": true, - "autoincrement": false, - "default": "true", - "generated": null, - "name": "auto_remount", - "entityType": "columns", - "table": "volumes_table" - }, - { - "type": "text", - "notNull": true, - "autoincrement": false, - "default": null, - "generated": null, - "name": "organization_id", - "entityType": "columns", - "table": "volumes_table" - }, - { - "columns": [ - "user_id" - ], - "tableTo": "users_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "account_user_id_users_table_id_fk", - "entityType": "fks", - "table": "account" - }, - { - "columns": [ - "schedule_id" - ], - "tableTo": "backup_schedules_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk", - "entityType": "fks", - "table": "backup_schedule_mirrors_table" - }, - { - "columns": [ - "repository_id" - ], - "tableTo": "repositories_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk", - "entityType": "fks", - "table": "backup_schedule_mirrors_table" - }, - { - "columns": [ - "schedule_id" - ], - "tableTo": "backup_schedules_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk", - "entityType": "fks", - "table": "backup_schedule_notifications_table" - }, - { - "columns": [ - "destination_id" - ], - "tableTo": "notification_destinations_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk", - "entityType": "fks", - "table": "backup_schedule_notifications_table" - }, - { - "columns": [ - "volume_id" - ], - "tableTo": "volumes_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "backup_schedules_table_volume_id_volumes_table_id_fk", - "entityType": "fks", - "table": "backup_schedules_table" - }, - { - "columns": [ - "repository_id" - ], - "tableTo": "repositories_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "backup_schedules_table_repository_id_repositories_table_id_fk", - "entityType": "fks", - "table": "backup_schedules_table" - }, - { - "columns": [ - "organization_id" - ], - "tableTo": "organization", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "backup_schedules_table_organization_id_organization_id_fk", - "entityType": "fks", - "table": "backup_schedules_table" - }, - { - "columns": [ - "organization_id" - ], - "tableTo": "organization", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "invitation_organization_id_organization_id_fk", - "entityType": "fks", - "table": "invitation" - }, - { - "columns": [ - "inviter_id" - ], - "tableTo": "users_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "invitation_inviter_id_users_table_id_fk", - "entityType": "fks", - "table": "invitation" - }, - { - "columns": [ - "organization_id" - ], - "tableTo": "organization", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "member_organization_id_organization_id_fk", - "entityType": "fks", - "table": "member" - }, - { - "columns": [ - "user_id" - ], - "tableTo": "users_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "member_user_id_users_table_id_fk", - "entityType": "fks", - "table": "member" - }, - { - "columns": [ - "organization_id" - ], - "tableTo": "organization", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "notification_destinations_table_organization_id_organization_id_fk", - "entityType": "fks", - "table": "notification_destinations_table" - }, - { - "columns": [ - "organization_id" - ], - "tableTo": "organization", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "repositories_table_organization_id_organization_id_fk", - "entityType": "fks", - "table": "repositories_table" - }, - { - "columns": [ - "user_id" - ], - "tableTo": "users_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "sessions_table_user_id_users_table_id_fk", - "entityType": "fks", - "table": "sessions_table" - }, - { - "columns": [ - "user_id" - ], - "tableTo": "users_table", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "two_factor_user_id_users_table_id_fk", - "entityType": "fks", - "table": "two_factor" - }, - { - "columns": [ - "organization_id" - ], - "tableTo": "organization", - "columnsTo": [ - "id" - ], - "onUpdate": "NO ACTION", - "onDelete": "CASCADE", - "nameExplicit": false, - "name": "volumes_table_organization_id_organization_id_fk", - "entityType": "fks", - "table": "volumes_table" - }, - { - "columns": [ - "schedule_id", - "destination_id" - ], - "nameExplicit": false, - "name": "backup_schedule_notifications_table_schedule_id_destination_id_pk", - "entityType": "pks", - "table": "backup_schedule_notifications_table" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "account_pk", - "table": "account", - "entityType": "pks" - }, - { - "columns": [ - "key" - ], - "nameExplicit": false, - "name": "app_metadata_pk", - "table": "app_metadata", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "backup_schedule_mirrors_table_pk", - "table": "backup_schedule_mirrors_table", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "backup_schedules_table_pk", - "table": "backup_schedules_table", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "invitation_pk", - "table": "invitation", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "member_pk", - "table": "member", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "notification_destinations_table_pk", - "table": "notification_destinations_table", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "organization_pk", - "table": "organization", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "repositories_table_pk", - "table": "repositories_table", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "sessions_table_pk", - "table": "sessions_table", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "two_factor_pk", - "table": "two_factor", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "users_table_pk", - "table": "users_table", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "verification_pk", - "table": "verification", - "entityType": "pks" - }, - { - "columns": [ - "id" - ], - "nameExplicit": false, - "name": "volumes_table_pk", - "table": "volumes_table", - "entityType": "pks" - }, - { - "columns": [ - { - "value": "user_id", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "account_userId_idx", - "entityType": "indexes", - "table": "account" - }, - { - "columns": [ - { - "value": "organization_id", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "invitation_organizationId_idx", - "entityType": "indexes", - "table": "invitation" - }, - { - "columns": [ - { - "value": "email", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "invitation_email_idx", - "entityType": "indexes", - "table": "invitation" - }, - { - "columns": [ - { - "value": "organization_id", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "member_organizationId_idx", - "entityType": "indexes", - "table": "member" - }, - { - "columns": [ - { - "value": "user_id", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "member_userId_idx", - "entityType": "indexes", - "table": "member" - }, - { - "columns": [ - { - "value": "organization_id", - "isExpression": false - }, - { - "value": "user_id", - "isExpression": false - } - ], - "isUnique": true, - "where": null, - "origin": "manual", - "name": "member_org_user_uidx", - "entityType": "indexes", - "table": "member" - }, - { - "columns": [ - { - "value": "slug", - "isExpression": false - } - ], - "isUnique": true, - "where": null, - "origin": "manual", - "name": "organization_slug_uidx", - "entityType": "indexes", - "table": "organization" - }, - { - "columns": [ - { - "value": "user_id", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "sessionsTable_userId_idx", - "entityType": "indexes", - "table": "sessions_table" - }, - { - "columns": [ - { - "value": "secret", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "twoFactor_secret_idx", - "entityType": "indexes", - "table": "two_factor" - }, - { - "columns": [ - { - "value": "user_id", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "twoFactor_userId_idx", - "entityType": "indexes", - "table": "two_factor" - }, - { - "columns": [ - { - "value": "identifier", - "isExpression": false - } - ], - "isUnique": false, - "where": null, - "origin": "manual", - "name": "verification_identifier_idx", - "entityType": "indexes", - "table": "verification" - }, - { - "columns": [ - "schedule_id", - "repository_id" - ], - "nameExplicit": false, - "name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique", - "entityType": "uniques", - "table": "backup_schedule_mirrors_table" - }, - { - "columns": [ - "name", - "organization_id" - ], - "nameExplicit": false, - "name": "volumes_table_name_organization_id_unique", - "entityType": "uniques", - "table": "volumes_table" - }, - { - "columns": [ - "short_id" - ], - "nameExplicit": false, - "name": "backup_schedules_table_short_id_unique", - "entityType": "uniques", - "table": "backup_schedules_table" - }, - { - "columns": [ - "short_id" - ], - "nameExplicit": false, - "name": "repositories_table_short_id_unique", - "entityType": "uniques", - "table": "repositories_table" - }, - { - "columns": [ - "token" - ], - "nameExplicit": false, - "name": "sessions_table_token_unique", - "entityType": "uniques", - "table": "sessions_table" - }, - { - "columns": [ - "username" - ], - "nameExplicit": false, - "name": "users_table_username_unique", - "entityType": "uniques", - "table": "users_table" - }, - { - "columns": [ - "email" - ], - "nameExplicit": false, - "name": "users_table_email_unique", - "entityType": "uniques", - "table": "users_table" - }, - { - "columns": [ - "short_id" - ], - "nameExplicit": false, - "name": "volumes_table_short_id_unique", - "entityType": "uniques", - "table": "volumes_table" - } - ], - "renames": [] -} \ No newline at end of file diff --git a/app/server/modules/lifecycle/migrations.ts b/app/server/modules/lifecycle/migrations.ts index 69147d49..91cb793c 100644 --- a/app/server/modules/lifecycle/migrations.ts +++ b/app/server/modules/lifecycle/migrations.ts @@ -2,6 +2,7 @@ import { logger } from "../../utils/logger"; import { v00001 } from "./migrations/00001-retag-snapshots"; import { v00002 } from "./migrations/00002-isolate-restic-passwords"; import { v00003 } from "./migrations/00003-assign-organization"; +import { v00004 } from "./migrations/00004-concat-path-name"; import { sql } from "drizzle-orm"; import { appMetadataTable, usersTable } from "../../db/schema"; import { db } from "../../db/db"; @@ -36,7 +37,7 @@ type MigrationEntity = { dependsOn?: string[]; }; -const registry: MigrationEntity[] = [v00001, v00002, v00003]; +const registry: MigrationEntity[] = [v00001, v00002, v00003, v00004]; export const runMigrations = async () => { const userCount = await db.select({ count: sql`count(*)` }).from(usersTable); diff --git a/app/server/modules/lifecycle/migrations/00004-concat-path-name.ts b/app/server/modules/lifecycle/migrations/00004-concat-path-name.ts new file mode 100644 index 00000000..82504e66 --- /dev/null +++ b/app/server/modules/lifecycle/migrations/00004-concat-path-name.ts @@ -0,0 +1,97 @@ +import { and, eq } from "drizzle-orm"; +import { db } from "../../../db/db"; +import { repositoriesTable } from "../../../db/schema"; +import { logger } from "../../../utils/logger"; +import { toMessage } from "~/server/utils/errors"; + +const DEFAULT_LOCAL_REPOSITORY_ROOT = "/var/lib/zerobyte/repositories"; + +type MigrationError = { name: string; error: string }; + +const asString = (value: unknown): string | null => { + if (typeof value !== "string") { + return null; + } + + return value; +}; + +const hasValue = (value: string | null): value is string => { + return value !== null && value.trim() !== ""; +}; + +const trimTrailingSlashes = (value: string): string => { + return value.replace(/\/+$/, ""); +}; + +const isPathAlreadyMigrated = (path: string, name: string): boolean => { + const normalizedPath = trimTrailingSlashes(path); + return normalizedPath.endsWith(`/${name}`); +}; + +const buildPath = (path: string | null, name: string): string => { + if (path === null || path.trim() === "") { + return `${DEFAULT_LOCAL_REPOSITORY_ROOT}/${name}`; + } + + return `${trimTrailingSlashes(path)}/${name}`; +}; + +const execute = async () => { + const errors: MigrationError[] = []; + const localRepositories = await db.query.repositoriesTable.findMany({ where: { type: "local" } }); + let migratedCount = 0; + + for (const repository of localRepositories) { + try { + const configValue = repository.config as unknown; + + if (typeof configValue !== "object" || configValue === null || Array.isArray(configValue)) { + errors.push({ + name: `repository:${repository.id}`, + error: "Repository config is not a valid JSON object", + }); + continue; + } + + const config = { ...(configValue as Record) }; + const localRepositoryName = asString(config.name); + + if (!hasValue(localRepositoryName)) { + continue; + } + + const currentPath = asString(config.path); + if (hasValue(currentPath) && isPathAlreadyMigrated(currentPath, localRepositoryName)) { + continue; + } + + config.path = buildPath(currentPath, localRepositoryName); + + await db + .update(repositoriesTable) + .set({ + config: config as typeof repository.config, + updatedAt: Date.now(), + }) + .where(and(eq(repositoriesTable.id, repository.id), eq(repositoriesTable.type, "local"))); + + migratedCount += 1; + } catch (err) { + errors.push({ + name: `repository:${repository.id}`, + error: toMessage(err), + }); + } + } + + logger.info(`Migration 00004-concat-path-name updated ${migratedCount} local repositories.`); + + return { success: errors.length === 0, errors }; +}; + +export const v00004 = { + execute, + id: "00004-concat-path-name", + type: "maintenance" as const, +};