-- ImageHub: Cloud Import — storage methods + external (referenced, not copied) dataset files. -- A storage method holds verified credentials (config_encrypted, never returned to the client) -- for an external bucket (S3/GCS/Azure). A dataset file is then EITHER a local content-addressed -- blob (blob_sha256 set) OR an external reference (storage_method_id + external_path set) that -- streams from the bucket and is never copied to our servers (privacy rule C4). Idempotent -- (CREATE/ADD ... IF NOT EXISTS) so the startup runner can apply it to volumes that predate it. -- Apply after 018 (no semicolons inside comments or string literals — the runner splitter is naive): -- docker exec -i initiative-postgres psql -U initiative -d initiatives < be0/migrations/019_imagehub_cloud_import.sql CREATE TABLE IF NOT EXISTS imagehub_storage_methods ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, provider TEXT NOT NULL CHECK (provider IN ('s3','gcs','azure')), access_mode TEXT NOT NULL DEFAULT 'read' CHECK (access_mode IN ('read','readwrite')), bucket TEXT NOT NULL, region TEXT, config_encrypted TEXT NOT NULL, verification_status TEXT NOT NULL DEFAULT 'pending' CHECK (verification_status IN ('pending','verified','failed')), verification_reason TEXT, verification_checked_at TIMESTAMPTZ, created_by UUID REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_imagehub_storage_methods_owner ON imagehub_storage_methods (owner_id); -- Allow a dataset file to be an external reference instead of a local blob. Existing rows keep -- blob_sha256 set and the new columns NULL, so they satisfy the local-blob branch of the CHECK. ALTER TABLE imagehub_dataset_files ALTER COLUMN blob_sha256 DROP NOT NULL; ALTER TABLE imagehub_dataset_files ADD COLUMN IF NOT EXISTS storage_method_id UUID REFERENCES imagehub_storage_methods(id) ON DELETE RESTRICT; ALTER TABLE imagehub_dataset_files ADD COLUMN IF NOT EXISTS external_path TEXT; -- A file is EITHER a local content-addressed blob OR an external reference, never both or neither. ALTER TABLE imagehub_dataset_files DROP CONSTRAINT IF EXISTS ck_imagehub_file_storage_mode; ALTER TABLE imagehub_dataset_files ADD CONSTRAINT ck_imagehub_file_storage_mode CHECK ( (blob_sha256 IS NOT NULL AND storage_method_id IS NULL AND external_path IS NULL) OR (blob_sha256 IS NULL AND storage_method_id IS NOT NULL AND external_path IS NOT NULL) ); CREATE INDEX IF NOT EXISTS idx_imagehub_dataset_files_storage_method ON imagehub_dataset_files (storage_method_id);