From ca0dfa5eaaf85fce114588cf85e8a334664ba860 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Thu, 13 Aug 2026 08:37:06 +0200 Subject: [PATCH 01/19] feat(db): add config jsonb column to databases --- src/db/migrations/0081_loud_black_widow.sql | 1 + src/db/migrations/meta/0081_snapshot.json | 3336 +++++++++++++++++++ src/db/migrations/meta/_journal.json | 7 + src/db/schema/07_database.ts | 3 +- 4 files changed, 3346 insertions(+), 1 deletion(-) create mode 100644 src/db/migrations/0081_loud_black_widow.sql create mode 100644 src/db/migrations/meta/0081_snapshot.json diff --git a/src/db/migrations/0081_loud_black_widow.sql b/src/db/migrations/0081_loud_black_widow.sql new file mode 100644 index 00000000..4f88b424 --- /dev/null +++ b/src/db/migrations/0081_loud_black_widow.sql @@ -0,0 +1 @@ +ALTER TABLE "databases" ADD COLUMN "config" jsonb; \ No newline at end of file diff --git a/src/db/migrations/meta/0081_snapshot.json b/src/db/migrations/meta/0081_snapshot.json new file mode 100644 index 00000000..e350d56b --- /dev/null +++ b/src/db/migrations/meta/0081_snapshot.json @@ -0,0 +1,3336 @@ +{ + "id": "7d56f8d6-8e8f-4ac3-b2ed-d2de1de8210e", + "prevId": "aa28c4ff-2b10-4981-a0dd-7f12c91d43e5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "smtp_password": { + "name": "smtp_password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "smtp_from": { + "name": "smtp_from", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "smtp_host": { + "name": "smtp_host", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "smtp_port": { + "name": "smtp_port", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "smtp_user": { + "name": "smtp_user", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "smtp_secure": { + "name": "smtp_secure", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "default_notification_channel_id": { + "name": "default_notification_channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "default_storage_channel_id": { + "name": "default_storage_channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "encryption": { + "name": "encryption", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "onboarding": { + "name": "onboarding", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "settings_default_notification_channel_id_notification_channel_id_fk": { + "name": "settings_default_notification_channel_id_notification_channel_id_fk", + "tableFrom": "settings", + "tableTo": "notification_channel", + "columnsFrom": [ + "default_notification_channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "settings_default_storage_channel_id_storage_channel_id_fk": { + "name": "settings_default_storage_channel_id_storage_channel_id_fk", + "tableFrom": "settings", + "tableTo": "storage_channel", + "columnsFrom": [ + "default_storage_channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_name_unique": { + "name": "settings_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey": { + "name": "passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deviceType": { + "name": "deviceType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backedUp": { + "name": "backedUp", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "passkey_user_id_user_id_fk": { + "name": "passkey_user_id_user_id_fk", + "tableFrom": "passkey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "failed_verification_count": { + "name": "failed_verification_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "locked_until": { + "name": "locked_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "theme": { + "name": "theme", + "type": "user_themes", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'light'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastConnectedAt": { + "name": "lastConnectedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastChangedPasswordAt": { + "name": "lastChangedPasswordAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "backup_policy": { + "name": "backup_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "projects_organization_id_organization_id_fk": { + "name": "projects_organization_id_organization_id_fk", + "tableFrom": "projects", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backups": { + "name": "backups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'waiting'" + }, + "file": { + "name": "file", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "imported": { + "name": "imported", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "migrated": { + "name": "migrated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_backups_status_core": { + "name": "idx_backups_status_core", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_backups_evolution": { + "name": "idx_backups_evolution", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'success' AND deleted_at IS NULL AND file_size IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "backups_database_id_databases_id_fk": { + "name": "backups_database_id_databases_id_fk", + "tableFrom": "backups", + "tableTo": "databases", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.databases": { + "name": "databases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_database_id": { + "name": "agent_database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dbms": { + "name": "dbms", + "type": "dbms_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backup_policy": { + "name": "backup_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_waiting_for_backup": { + "name": "is_waiting_for_backup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "backup_to_restore": { + "name": "backup_to_restore", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_error_count": { + "name": "health_error_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_contact": { + "name": "last_contact", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_databases_availability": { + "name": "idx_databases_availability", + "columns": [ + { + "expression": "last_contact", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "deleted_at IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "databases_agent_id_agents_id_fk": { + "name": "databases_agent_id_agents_id_fk", + "tableFrom": "databases", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "databases_project_id_projects_id_fk": { + "name": "databases_project_id_projects_id_fk", + "tableFrom": "databases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restorations": { + "name": "restorations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'waiting'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "backup_storage_id": { + "name": "backup_storage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "backup_id": { + "name": "backup_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "restorations_backup_storage_id_backup_storage_id_fk": { + "name": "restorations_backup_storage_id_backup_storage_id_fk", + "tableFrom": "restorations", + "tableTo": "backup_storage", + "columnsFrom": [ + "backup_storage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "restorations_backup_id_backups_id_fk": { + "name": "restorations_backup_id_backups_id_fk", + "tableFrom": "restorations", + "tableTo": "backups", + "columnsFrom": [ + "backup_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "restorations_database_id_databases_id_fk": { + "name": "restorations_database_id_databases_id_fk", + "tableFrom": "restorations", + "tableTo": "databases", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.retention_policies": { + "name": "retention_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "retention_policy_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "days": { + "name": "days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "gfs_hourly": { + "name": "gfs_hourly", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "gfs_daily": { + "name": "gfs_daily", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "gfs_weekly": { + "name": "gfs_weekly", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 4 + }, + "gfs_monthly": { + "name": "gfs_monthly", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 12 + }, + "gfs_yearly": { + "name": "gfs_yearly", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "retention_policies_database_id_databases_id_fk": { + "name": "retention_policies_database_id_databases_id_fk", + "tableFrom": "retention_policies", + "tableTo": "databases", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "retention_policies_project_id_projects_id_fk": { + "name": "retention_policies_project_id_projects_id_fk", + "tableFrom": "retention_policies", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "retention_policy_owner_xor": { + "name": "retention_policy_owner_xor", + "value": "num_nonnulls(\"retention_policies\".\"database_id\", \"retention_policies\".\"project_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "health_error_count": { + "name": "health_error_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "override_url": { + "name": "override_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "last_contact": { + "name": "last_contact", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_agents_availability": { + "name": "idx_agents_availability", + "columns": [ + { + "expression": "last_contact", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "is_archived = false AND deleted_at IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_organization_id_organization_id_fk": { + "name": "agents_organization_id_organization_id_fk", + "tableFrom": "agents", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_slug_unique": { + "name": "agents_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_agents": { + "name": "organization_agents", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_agents_organization_id_organization_id_fk": { + "name": "organization_agents_organization_id_organization_id_fk", + "tableFrom": "organization_agents", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_agents_agent_id_agents_id_fk": { + "name": "organization_agents_agent_id_agents_id_fk", + "tableFrom": "organization_agents", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_agents_organization_id_agent_id_unique": { + "name": "organization_agents_organization_id_agent_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "agent_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_channel": { + "name": "notification_channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "provider_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channel_organization_id_organization_id_fk": { + "name": "notification_channel_organization_id_organization_id_fk", + "tableFrom": "notification_channel", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_notification_channels": { + "name": "organization_notification_channels", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "notification_channel_id": { + "name": "notification_channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_notification_channels_organization_id_organization_id_fk": { + "name": "organization_notification_channels_organization_id_organization_id_fk", + "tableFrom": "organization_notification_channels", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_notification_channels_notification_channel_id_notification_channel_id_fk": { + "name": "organization_notification_channels_notification_channel_id_notification_channel_id_fk", + "tableFrom": "organization_notification_channels", + "tableTo": "notification_channel", + "columnsFrom": [ + "notification_channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_notification_channels_organization_id_notification_channel_id_unique": { + "name": "organization_notification_channels_organization_id_notification_channel_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "notification_channel_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.alert_policy": { + "name": "alert_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "notification_channel_id": { + "name": "notification_channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_kind": { + "name": "event_kind", + "type": "event_kind[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "alert_policy_notification_channel_id_notification_channel_id_fk": { + "name": "alert_policy_notification_channel_id_notification_channel_id_fk", + "tableFrom": "alert_policy", + "tableTo": "notification_channel", + "columnsFrom": [ + "notification_channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "alert_policy_database_id_databases_id_fk": { + "name": "alert_policy_database_id_databases_id_fk", + "tableFrom": "alert_policy", + "tableTo": "databases", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "alert_policy_project_id_projects_id_fk": { + "name": "alert_policy_project_id_projects_id_fk", + "tableFrom": "alert_policy", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "alert_policy_owner_xor": { + "name": "alert_policy_owner_xor", + "value": "num_nonnulls(\"alert_policy\".\"database_id\", \"alert_policy\".\"project_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.notification_log": { + "name": "notification_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_name": { + "name": "provider_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_response": { + "name": "provider_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_notif_log_critical_24h": { + "name": "idx_notif_log_critical_24h", + "columns": [ + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "event IN ('error_backup', 'error_restore', 'error_health_agent', 'error_health_database')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_storage_channels": { + "name": "organization_storage_channels", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "storage_channel_id": { + "name": "storage_channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_storage_channels_organization_id_organization_id_fk": { + "name": "organization_storage_channels_organization_id_organization_id_fk", + "tableFrom": "organization_storage_channels", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_storage_channels_storage_channel_id_storage_channel_id_fk": { + "name": "organization_storage_channels_storage_channel_id_storage_channel_id_fk", + "tableFrom": "organization_storage_channels", + "tableTo": "storage_channel", + "columnsFrom": [ + "storage_channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_storage_channels_organization_id_storage_channel_id_unique": { + "name": "organization_storage_channels_organization_id_storage_channel_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "storage_channel_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storage_channel": { + "name": "storage_channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "provider_storage_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "storage_channel_organization_id_organization_id_fk": { + "name": "storage_channel_organization_id_organization_id_fk", + "tableFrom": "storage_channel", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storage_policy": { + "name": "storage_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "storage_channel_id": { + "name": "storage_channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "storage_policy_storage_channel_id_storage_channel_id_fk": { + "name": "storage_policy_storage_channel_id_storage_channel_id_fk", + "tableFrom": "storage_policy", + "tableTo": "storage_channel", + "columnsFrom": [ + "storage_channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "storage_policy_database_id_databases_id_fk": { + "name": "storage_policy_database_id_databases_id_fk", + "tableFrom": "storage_policy", + "tableTo": "databases", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "storage_policy_project_id_projects_id_fk": { + "name": "storage_policy_project_id_projects_id_fk", + "tableFrom": "storage_policy", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "storage_policy_owner_xor": { + "name": "storage_policy_owner_xor", + "value": "num_nonnulls(\"storage_policy\".\"database_id\", \"storage_policy\".\"project_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.backup_storage": { + "name": "backup_storage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "backup_id": { + "name": "backup_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "storage_channel_id": { + "name": "storage_channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "backup_storage_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_backup_storage_treemap": { + "name": "idx_backup_storage_treemap", + "columns": [ + { + "expression": "storage_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'success' AND size IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "backup_storage_backup_id_backups_id_fk": { + "name": "backup_storage_backup_id_backups_id_fk", + "tableFrom": "backup_storage", + "tableTo": "backups", + "columnsFrom": [ + "backup_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_storage_storage_channel_id_storage_channel_id_fk": { + "name": "backup_storage_storage_channel_id_storage_channel_id_fk", + "tableFrom": "backup_storage", + "tableTo": "storage_channel", + "columnsFrom": [ + "storage_channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.healthcheck_log": { + "name": "healthcheck_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kind": { + "name": "kind", + "type": "healthcheck_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "healthcheck_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_log": { + "name": "job_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "backup_id": { + "name": "backup_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "restoration_id": { + "name": "restoration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "logged_at": { + "name": "logged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "entry_type": { + "name": "entry_type", + "type": "job_log_entry_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "job_log_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "job_log_backup_id_backups_id_fk": { + "name": "job_log_backup_id_backups_id_fk", + "tableFrom": "job_log", + "tableTo": "backups", + "columnsFrom": [ + "backup_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "job_log_restoration_id_restorations_id_fk": { + "name": "job_log_restoration_id_restorations_id_fk", + "tableFrom": "job_log", + "tableTo": "restorations", + "columnsFrom": [ + "restoration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.user_themes": { + "name": "user_themes", + "schema": "public", + "values": [ + "light", + "dark", + "system" + ] + }, + "public.retention_policy_type": { + "name": "retention_policy_type", + "schema": "public", + "values": [ + "count", + "days", + "gfs" + ] + }, + "public.provider_kind": { + "name": "provider_kind", + "schema": "public", + "values": [ + "slack", + "smtp", + "discord", + "telegram", + "gotify", + "ntfy", + "webhook", + "nextcloud", + "teams", + "pushover", + "apprise" + ] + }, + "public.event_kind": { + "name": "event_kind", + "schema": "public", + "values": [ + "error_backup", + "error_restore", + "success_restore", + "success_backup", + "weekly_report", + "error_health_agent", + "error_health_database" + ] + }, + "public.level": { + "name": "level", + "schema": "public", + "values": [ + "critical", + "warning", + "info" + ] + }, + "public.provider_storage_kind": { + "name": "provider_storage_kind", + "schema": "public", + "values": [ + "local", + "s3", + "google-drive", + "google-cloud-storage", + "blob" + ] + }, + "public.backup_storage_status": { + "name": "backup_storage_status", + "schema": "public", + "values": [ + "pending", + "success", + "failed" + ] + }, + "public.healthcheck_status": { + "name": "healthcheck_status", + "schema": "public", + "values": [ + "success", + "failed" + ] + }, + "public.healthcheck_kind": { + "name": "healthcheck_kind", + "schema": "public", + "values": [ + "database", + "agent" + ] + }, + "public.job_log_entry_type": { + "name": "job_log_entry_type", + "schema": "public", + "values": [ + "log", + "command" + ] + }, + "public.job_log_level": { + "name": "job_log_level", + "schema": "public", + "values": [ + "debug", + "info", + "warn", + "error" + ] + }, + "public.dbms_status": { + "name": "dbms_status", + "schema": "public", + "values": [ + "postgresql", + "postgresql-cluster", + "mysql", + "mariadb", + "mongodb", + "sqlite", + "redis", + "valkey", + "firebird", + "mssql", + "docker-volume" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "waiting", + "ongoing", + "failed", + "success" + ] + }, + "public.type_storage": { + "name": "type_storage", + "schema": "public", + "values": [ + "local", + "s3" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.mv_kpi_backup_counts": { + "columns": { + "available_count": { + "name": "available_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "total_done": { + "name": "total_done", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "possession_rate_pct": { + "name": "possession_rate_pct", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "singleton": { + "name": "singleton", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n COUNT(*) FILTER (WHERE status = 'success' AND deleted_at IS NULL) AS available_count,\n COUNT(*) FILTER (WHERE status IN ('success', 'failed')) AS total_done,\n ROUND(\n COUNT(*) FILTER (WHERE status = 'success' AND deleted_at IS NULL)::numeric\n / NULLIF(COUNT(*) FILTER (WHERE status IN ('success', 'failed')), 0) * 100, 1\n ) AS possession_rate_pct,\n 1 AS singleton\n FROM backups\n", + "name": "mv_kpi_backup_counts", + "schema": "public", + "isExisting": false, + "materialized": true + }, + "public.mv_kpi_dbms_treemap": { + "columns": { + "dbms": { + "name": "dbms", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "database_count": { + "name": "database_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "backup_count": { + "name": "backup_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n db.dbms,\n SUM(b.file_size) AS total_bytes,\n COUNT(DISTINCT db.id) AS database_count,\n COUNT(b.id) AS backup_count\n FROM backups b\n JOIN databases db ON db.id = b.database_id\n WHERE b.status = 'success' AND b.deleted_at IS NULL AND b.file_size IS NOT NULL\n GROUP BY db.dbms\n ORDER BY total_bytes DESC\n", + "name": "mv_kpi_dbms_treemap", + "schema": "public", + "isExisting": false, + "materialized": true + }, + "public.mv_kpi_evolution_monthly": { + "columns": { + "period": { + "name": "period", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "success_count": { + "name": "success_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "failed_count": { + "name": "failed_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n DATE_TRUNC('day', created_at) AS period,\n COALESCE(\n SUM(file_size) FILTER (\n WHERE status = 'success' AND deleted_at IS NULL AND file_size IS NOT NULL\n ), 0\n ) AS total_bytes,\n COUNT(*) FILTER (WHERE status = 'success') AS success_count,\n COUNT(*) FILTER (WHERE status = 'failed') AS failed_count\n FROM backups\n WHERE status IN ('success', 'failed')\n GROUP BY DATE_TRUNC('day', created_at)\n ORDER BY period ASC\n", + "name": "mv_kpi_evolution_monthly", + "schema": "public", + "isExisting": false, + "materialized": true + }, + "public.mv_kpi_storage_treemap": { + "columns": { + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "channel_name": { + "name": "channel_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "backup_count": { + "name": "backup_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n sc.id AS channel_id,\n sc.name AS channel_name,\n sc.provider,\n SUM(bs.size) AS total_bytes,\n COUNT(bs.id) AS backup_count\n FROM backup_storage bs\n JOIN storage_channel sc ON sc.id = bs.storage_channel_id\n JOIN backups b ON b.id = bs.backup_id\n WHERE bs.status = 'success' AND bs.size IS NOT NULL AND b.deleted_at IS NULL\n GROUP BY sc.id, sc.name, sc.provider\n ORDER BY total_bytes DESC\n", + "name": "mv_kpi_storage_treemap", + "schema": "public", + "isExisting": false, + "materialized": true + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 8502a9f7..deea886f 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -568,6 +568,13 @@ "when": 1785824737806, "tag": "0080_sparkling_gamma_corps", "breakpoints": true + }, + { + "idx": 81, + "version": "7", + "when": 1786602822645, + "tag": "0081_loud_black_widow", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schema/07_database.ts b/src/db/schema/07_database.ts index fef2ec93..233649c2 100644 --- a/src/db/schema/07_database.ts +++ b/src/db/schema/07_database.ts @@ -1,4 +1,4 @@ -import {pgTable, text, boolean, timestamp, uuid, integer, pgEnum, bigint, index, check} from "drizzle-orm/pg-core"; +import {pgTable, text, boolean, timestamp, uuid, integer, pgEnum, bigint, index, check, jsonb} from "drizzle-orm/pg-core"; import {Agent, agent, AgentWith} from "./08_agent"; import {Project, project} from "./06_project"; import {relations, sql} from "drizzle-orm"; @@ -20,6 +20,7 @@ export const database = pgTable("databases", { backupPolicy: text("backup_policy"), isWaitingForBackup: boolean("is_waiting_for_backup").default(false).notNull(), backupToRestore: text("backup_to_restore"), + config: jsonb("config"), healthErrorCount: integer("health_error_count"), agentId: uuid("agent_id") .references(() => agent.id, {onDelete: "cascade"}), From f1de8d04791ff57035faa11fd5a256860aa3215d Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Thu, 13 Aug 2026 08:41:22 +0200 Subject: [PATCH 02/19] fix(db): type config jsonb column to align row/zod types .$type>() is TS-only; no SQL/migration change. Fixes the drizzle raw-row `config: unknown` vs drizzle-zod `config: Json` mismatch that caused 22 spurious tsc errors (51 -> 73). tsc is back to the pre-existing 51-error baseline. --- src/db/schema/07_database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/schema/07_database.ts b/src/db/schema/07_database.ts index 233649c2..8bb4410f 100644 --- a/src/db/schema/07_database.ts +++ b/src/db/schema/07_database.ts @@ -20,7 +20,7 @@ export const database = pgTable("databases", { backupPolicy: text("backup_policy"), isWaitingForBackup: boolean("is_waiting_for_backup").default(false).notNull(), backupToRestore: text("backup_to_restore"), - config: jsonb("config"), + config: jsonb("config").$type>(), healthErrorCount: integer("health_error_count"), agentId: uuid("agent_id") .references(() => agent.id, {onDelete: "cascade"}), From dbee3c9bf8926d36f57fc1117c75984dae9525ee Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Thu, 13 Aug 2026 08:48:13 +0200 Subject: [PATCH 03/19] feat(database): zod config schema + field defs for all dbms types --- .../schemas/database-config.schema.ts | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/features/database/schemas/database-config.schema.ts diff --git a/src/features/database/schemas/database-config.schema.ts b/src/features/database/schemas/database-config.schema.ts new file mode 100644 index 00000000..32957a99 --- /dev/null +++ b/src/features/database/schemas/database-config.schema.ts @@ -0,0 +1,149 @@ +import { z } from "zod"; +import { EDbmsSchema } from "@/db/schema/types"; + +// ---- shared field pieces (snake_case keys match the agent's InputDatabaseConfig) ---- +const host = z.string().min(1, "Host is required"); +const port = z.coerce.number().int().min(1, "Port must be >= 1").max(65535, "Port must be <= 65535"); +const username = z.string().min(1, "Username is required"); +const password = z.string().min(1, "Password is required"); +const databaseName = z.string().min(1, "Database is required"); + +const PostgresOptionsSchema = z + .object({ + keep_ownership: z.boolean().optional().default(false), + clean_mode: z + .enum(["none", "clean", "drop_schemas", "drop_database"]) + .optional() + .default("clean"), + }) + .optional(); + +// ---- per-type config schemas ---- +export const PostgresqlConfigSchema = z.object({ + host, port, username, password, database: databaseName, options: PostgresOptionsSchema, +}); +export const PostgresqlClusterConfigSchema = z.object({ + host, port, username, password, + database: z.string().optional().default("postgres"), + options: PostgresOptionsSchema, +}); +export const MysqlConfigSchema = z.object({ + host, port, username, password, database: databaseName, + max_packet_size: z.string().optional().default("512M"), +}); +export const MariadbConfigSchema = MysqlConfigSchema; +export const MssqlConfigSchema = z.object({ + host, port, username, password, database: databaseName, +}); +export const MongodbConfigSchema = z.object({ + host, port, database: databaseName, + username: z.string().optional(), password: z.string().optional(), +}); +export const RedisConfigSchema = z.object({ + host, port, + database: z.string().optional(), + username: z.string().optional(), password: z.string().optional(), +}); +export const ValkeyConfigSchema = RedisConfigSchema; +export const FirebirdConfigSchema = z.object({ + host, port, database: databaseName, + username: z.string().optional(), password: z.string().optional(), +}); +export const SqliteConfigSchema = z.object({ + path: z.string().min(1, "Path is required"), + database: z.string().optional(), +}); +export const DockerVolumeConfigSchema = z.object({ + volume_name: z.string().min(1, "Volume name is required"), + container_name: z.string().optional(), +}); + +// ---- top-level discriminated union (name + dbms are columns; config is jsonb) ---- +const BaseDb = z.object({ + name: z.string().min(1, "Name is required").max(255), +}); + +export const DatabaseConfigFormSchema = z.discriminatedUnion("dbms", [ + BaseDb.extend({ dbms: z.literal("postgresql"), config: PostgresqlConfigSchema }), + BaseDb.extend({ dbms: z.literal("postgresql-cluster"), config: PostgresqlClusterConfigSchema }), + BaseDb.extend({ dbms: z.literal("mysql"), config: MysqlConfigSchema }), + BaseDb.extend({ dbms: z.literal("mariadb"), config: MariadbConfigSchema }), + BaseDb.extend({ dbms: z.literal("mssql"), config: MssqlConfigSchema }), + BaseDb.extend({ dbms: z.literal("mongodb"), config: MongodbConfigSchema }), + BaseDb.extend({ dbms: z.literal("redis"), config: RedisConfigSchema }), + BaseDb.extend({ dbms: z.literal("valkey"), config: ValkeyConfigSchema }), + BaseDb.extend({ dbms: z.literal("firebird"), config: FirebirdConfigSchema }), + BaseDb.extend({ dbms: z.literal("sqlite"), config: SqliteConfigSchema }), + BaseDb.extend({ dbms: z.literal("docker-volume"), config: DockerVolumeConfigSchema }), +]); + +export type DatabaseConfigFormType = z.infer; + +// ---- data-driven form field definitions ---- +export type FieldDef = { + name: string; // dot path relative to the form root, e.g. "config.host" + label: string; + widget: "text" | "password" | "number" | "switch" | "select"; + placeholder?: string; + options?: { value: string; label: string }[]; +}; + +const f = (name: string, label: string, widget: FieldDef["widget"], placeholder?: string, options?: FieldDef["options"]): FieldDef => + ({ name: `config.${name}`, label, widget, placeholder, options }); + +const HOST = f("host", "Host *", "text", "e.g. postgres"); +const PORT = f("port", "Port *", "number", "e.g. 5432"); +const USER = f("username", "Username *", "text", "e.g. postgres"); +const USER_OPT = f("username", "Username", "text", "optional"); +const PASS = f("password", "Password *", "password", "••••••••"); +const PASS_OPT = f("password", "Password", "password", "optional"); +const DB = f("database", "Database *", "text", "e.g. app_db"); +const DB_OPT = f("database", "Database", "text", "optional"); +const KEEP_OWNERSHIP = f("options.keep_ownership", "Keep ownership", "switch"); +const CLEAN_MODE = f("options.clean_mode", "Clean mode", "select", undefined, [ + { value: "none", label: "none" }, + { value: "clean", label: "clean (default)" }, + { value: "drop_schemas", label: "drop_schemas" }, + { value: "drop_database", label: "drop_database" }, +]); + +export const databaseFieldDefs: Record = { + postgresql: [HOST, PORT, USER, PASS, DB, KEEP_OWNERSHIP, CLEAN_MODE], + "postgresql-cluster": [HOST, PORT, USER, PASS, f("database", "Database", "text", "defaults to postgres"), KEEP_OWNERSHIP, CLEAN_MODE], + mysql: [HOST, PORT, USER, PASS, DB, f("max_packet_size", "Max packet size", "text", "512M")], + mariadb: [HOST, PORT, USER, PASS, DB, f("max_packet_size", "Max packet size", "text", "512M")], + mssql: [HOST, PORT, USER, PASS, DB], + mongodb: [HOST, PORT, DB, USER_OPT, PASS_OPT], + redis: [HOST, PORT, DB_OPT, USER_OPT, PASS_OPT], + valkey: [HOST, PORT, DB_OPT, USER_OPT, PASS_OPT], + firebird: [HOST, PORT, DB, USER_OPT, PASS_OPT], + sqlite: [f("path", "Path *", "text", "/data/app.db"), DB_OPT], + "docker-volume": [f("volume_name", "Volume name *", "text", "e.g. postgres_data"), f("container_name", "Container name", "text", "optional")], +}; + +export const databaseTypeOptions: { value: EDbmsSchema; label: string }[] = [ + { value: "postgresql", label: "PostgreSQL" }, + { value: "postgresql-cluster", label: "PostgreSQL Cluster" }, + { value: "mysql", label: "MySQL" }, + { value: "mariadb", label: "MariaDB" }, + { value: "mssql", label: "MSSQL" }, + { value: "mongodb", label: "MongoDB" }, + { value: "redis", label: "Redis" }, + { value: "valkey", label: "Valkey" }, + { value: "firebird", label: "Firebird" }, + { value: "sqlite", label: "SQLite" }, + { value: "docker-volume", label: "Docker Volume" }, +]; + +export function defaultConfigFor(dbms: EDbmsSchema): Record { + switch (dbms) { + case "postgresql": + case "postgresql-cluster": + return { options: { keep_ownership: false, clean_mode: "clean" } }; + case "mysql": + case "mariadb": + return { max_packet_size: "512M" }; + default: + return {}; + } +} From 28c24a35ca1698f2dd828a6ef2135af474ac0554 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Thu, 13 Aug 2026 08:51:44 +0200 Subject: [PATCH 04/19] feat(database): data-driven config field renderer --- .../config/database-config-fields.tsx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/features/database/components/config/database-config-fields.tsx diff --git a/src/features/database/components/config/database-config-fields.tsx b/src/features/database/components/config/database-config-fields.tsx new file mode 100644 index 00000000..33be9200 --- /dev/null +++ b/src/features/database/components/config/database-config-fields.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { UseFormReturn } from "react-hook-form"; +import { EDbmsSchema } from "@/db/schema/types"; +import { databaseFieldDefs } from "@/features/database/schemas/database-config.schema"; +import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { PasswordInput } from "@/components/ui/password-input"; +import { Switch } from "@/components/ui/switch"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; + +type Props = { dbms: EDbmsSchema; form: UseFormReturn }; + +export const DatabaseConfigFields = ({ dbms, form }: Props) => { + const defs = databaseFieldDefs[dbms] ?? []; + return ( +
+ {defs.map((def) => ( + ( + + {def.label} + + {def.widget === "password" ? ( + + ) : def.widget === "switch" ? ( + + ) : def.widget === "select" ? ( + + ) : ( + + )} + + + + )} + /> + ))} +
+ ); +}; From 732c30be6671d37c1e7945fa1b36a03e32b5d0b4 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Thu, 13 Aug 2026 08:54:41 +0200 Subject: [PATCH 05/19] feat(database): upsert config server action + agent-scope acl --- .../actions/database-config.action.ts | 47 +++++++++++++++++++ src/features/database/utils/database-acl.ts | 33 +++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/features/database/actions/database-config.action.ts diff --git a/src/features/database/actions/database-config.action.ts b/src/features/database/actions/database-config.action.ts new file mode 100644 index 00000000..4bb58244 --- /dev/null +++ b/src/features/database/actions/database-config.action.ts @@ -0,0 +1,47 @@ +"use server"; + +import { z } from "zod"; +import { db } from "@/db"; +import * as drizzleDb from "@/db"; +import { and, eq } from "drizzle-orm"; +import { userAction, ActionError } from "@/lib/safe-actions/actions"; +import { withUpdatedAt } from "@/db/utils"; +import { DatabaseConfigFormSchema } from "@/features/database/schemas/database-config.schema"; +import { assertCanManageAgentDatabases } from "@/features/database/utils/database-acl"; + +export const upsertDatabaseConfigAction = userAction + .inputSchema( + z.object({ + agentId: z.string().uuid(), + databaseId: z.string().uuid().optional(), + data: DatabaseConfigFormSchema, + }), + ) + .action(async ({ parsedInput }) => { + const { agentId, databaseId, data } = parsedInput; + + try { + await assertCanManageAgentDatabases(agentId); + } catch (e) { + throw new ActionError(e instanceof Error ? e.message : "Not authorized"); + } + + if (databaseId) { + const [updated] = await db + .update(drizzleDb.schemas.database) + .set(withUpdatedAt({ name: data.name, dbms: data.dbms, config: data.config })) + .where(and( + eq(drizzleDb.schemas.database.id, databaseId), + eq(drizzleDb.schemas.database.agentId, agentId), + )) + .returning(); + if (!updated) throw new ActionError("Database not found"); + return updated; + } + + const [created] = await db + .insert(drizzleDb.schemas.database) + .values({ agentId, name: data.name, dbms: data.dbms, config: data.config }) + .returning(); + return created; + }); diff --git a/src/features/database/utils/database-acl.ts b/src/features/database/utils/database-acl.ts index ee6a655d..69613752 100644 --- a/src/features/database/utils/database-acl.ts +++ b/src/features/database/utils/database-acl.ts @@ -70,3 +70,36 @@ export async function assertCanDeleteDatabase(databaseId: string): Promise throw new AgentOnlineError(databaseId); } } + +export async function assertCanManageAgentDatabases(agentId: string): Promise { + const agent = await db.query.agent.findFirst({ + where: eq(drizzleDb.schemas.agent.id, agentId), + }); + if (!agent) { + throw new DatabaseNotFoundError(agentId); + } + + const user = await currentUser(); + if (!user) { + throw new UnauthorizedError(agentId); + } + + const isAdmin = user.role === "superadmin" || user.role === "admin"; + + let authorized: boolean; + if (agent.organizationId === null) { + authorized = isAdmin; + } else { + const organization = await getOrganization({}); + const activeMember = await getActiveMember(); + const canManage = activeMember + ? computeOrganizationPermissions(activeMember).canManageAgents + : false; + const hasAccess = !!organization && agent.organizationId === organization.id; + authorized = canManage && hasAccess; + } + + if (!authorized) { + throw new UnauthorizedError(agentId); + } +} From 3f73e665cfda351027c8af12b83ec428edb3bed9 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Thu, 13 Aug 2026 08:58:12 +0200 Subject: [PATCH 06/19] feat(database): config modal with form/json tabs and two-way sync --- .../components/database-config-modal.tsx | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/features/database/components/database-config-modal.tsx diff --git a/src/features/database/components/database-config-modal.tsx b/src/features/database/components/database-config-modal.tsx new file mode 100644 index 00000000..0d4080e2 --- /dev/null +++ b/src/features/database/components/database-config-modal.tsx @@ -0,0 +1,160 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { useMutation } from "@tanstack/react-query"; +import { toast } from "sonner"; +import { + Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, +} from "@/components/ui/dialog"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, SelectContent, SelectItem, SelectTrigger, SelectValue, +} from "@/components/ui/select"; +import { ButtonWithLoading } from "@/components/common/button-with-loading"; +import { Database } from "@/db/schema/07_database"; +import { EDbmsSchema } from "@/db/schema/types"; +import { + DatabaseConfigFormSchema, DatabaseConfigFormType, databaseTypeOptions, defaultConfigFor, +} from "@/features/database/schemas/database-config.schema"; +import { DatabaseConfigFields } from "@/features/database/components/config/database-config-fields"; +import { upsertDatabaseConfigAction } from "@/features/database/actions/database-config.action"; + +type Props = { agentId: string; database?: Database; trigger: React.ReactNode }; + +export const DatabaseConfigModal = ({ agentId, database, trigger }: Props) => { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [tab, setTab] = useState<"form" | "json">("form"); + const [jsonText, setJsonText] = useState(""); + const [jsonError, setJsonError] = useState(null); + + const form = useZodForm({ + schema: DatabaseConfigFormSchema, + defaultValues: database + ? { name: database.name, dbms: database.dbms, config: (database.config as any) ?? {} } + : { name: "", dbms: "postgresql", config: defaultConfigFor("postgresql") }, + }); + + const dbms = form.watch("dbms") as EDbmsSchema; + const watched = form.watch(); + + // Form -> JSON: refresh textarea from form values while on the Form tab. + useEffect(() => { + if (tab === "form") setJsonText(JSON.stringify(watched, null, 2)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(watched), tab]); + + // JSON -> Form: parse on every edit; reset the form on success, show error on failure. + const onJsonChange = (text: string) => { + setJsonText(text); + try { + const parsed = JSON.parse(text); + setJsonError(null); + form.reset(parsed); + } catch (e) { + setJsonError((e as Error).message); + } + }; + + const onDbmsChange = (value: EDbmsSchema) => { + // @ts-expect-error — defaultValues type is a union across discriminated dbms variants + form.reset({ name: form.getValues("name"), dbms: value, config: defaultConfigFor(value) }); + }; + + const mutation = useMutation({ + mutationFn: async (values: DatabaseConfigFormType) => { + const result = await upsertDatabaseConfigAction({ + agentId, + databaseId: database?.id, + data: values, + }); + if (result?.serverError) { + toast.error(result.serverError); + return; + } + toast.success(database ? "Database updated" : "Database created"); + setOpen(false); + router.refresh(); + }, + }); + + return ( + + {trigger} + + + {database ? "Configure database" : "Add database"} + + Define the connection settings pushed to the agent on status. + + + +
{ await mutation.mutateAsync(v); }}> + ( + + Name * + + + + + + )} + /> + + ( + + Type * + + + + )} + /> + + setTab(v as "form" | "json")}> + + Form + JSON + + + + + + + +