feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
-- =====================================================================
|
||||
-- FILE_MGR SCHEMA — File Manager, Storage Quotas, Cleanup Scheduler
|
||||
-- =====================================================================
|
||||
|
||||
SET search_path TO file_mgr, public;
|
||||
|
||||
CREATE TYPE file_kind AS ENUM ('Video','Image','Audio','Voiceover','Document','Other');
|
||||
CREATE TYPE folder_kind AS ENUM ('System','User','Shared','Tenant');
|
||||
CREATE TYPE upload_status AS ENUM ('Pending','Uploading','Processing','Ready','Failed','Quarantined');
|
||||
CREATE TYPE cleanup_entity_type AS ENUM ('Export','TempRenderFolder','OrphanedFile','UnusedUpload','SnapshotExpired');
|
||||
CREATE TYPE cleanup_status AS ENUM ('Scheduled','Notified','Processing','Done','Skipped','Failed');
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- user_folders — hierarchical folders
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE user_folders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
folder_type folder_kind NOT NULL DEFAULT 'User',
|
||||
parent_folder_id UUID REFERENCES user_folders(id) ON DELETE CASCADE,
|
||||
|
||||
-- Stats (denormalized for fast UI)
|
||||
file_count INT NOT NULL DEFAULT 0,
|
||||
total_size_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
sort INT NOT NULL DEFAULT 0,
|
||||
is_shared BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
share_token TEXT,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_folders_user ON user_folders(user_id, parent_folder_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_folders_parent ON user_folders(parent_folder_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_folders_share ON user_folders(share_token) WHERE share_token IS NOT NULL;
|
||||
|
||||
CREATE TRIGGER tg_folders_updated_at
|
||||
BEFORE UPDATE ON user_folders FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- user_files
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE user_files (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
user_folder_id UUID REFERENCES user_folders(id) ON DELETE SET NULL,
|
||||
|
||||
-- Identity
|
||||
name TEXT NOT NULL,
|
||||
original_filename TEXT,
|
||||
file_extension TEXT,
|
||||
mime_type TEXT,
|
||||
file_type file_kind NOT NULL,
|
||||
|
||||
-- Storage
|
||||
minio_bucket TEXT NOT NULL,
|
||||
minio_key TEXT NOT NULL,
|
||||
cdn_url TEXT,
|
||||
file_address TEXT NOT NULL, -- canonical URL
|
||||
size_bytes BIGINT NOT NULL,
|
||||
md5_hash TEXT,
|
||||
sha256_hash TEXT,
|
||||
|
||||
-- Media metadata
|
||||
duration_sec NUMERIC(8,2),
|
||||
width INT,
|
||||
height INT,
|
||||
fps NUMERIC(5,2),
|
||||
bitrate_kbps INT,
|
||||
codec TEXT,
|
||||
has_audio BOOLEAN,
|
||||
has_video BOOLEAN,
|
||||
|
||||
-- Thumbnails
|
||||
thumbnail_url TEXT,
|
||||
waveform_data JSONB, -- for audio files
|
||||
|
||||
-- Upload state
|
||||
upload_status upload_status NOT NULL DEFAULT 'Ready',
|
||||
upload_id TEXT, -- multipart upload ID if used
|
||||
upload_progress INT NOT NULL DEFAULT 100,
|
||||
processing_error TEXT,
|
||||
|
||||
-- Source / linkage
|
||||
source TEXT, -- 'upload','export','snapshot','voiceover_record','stock'
|
||||
export_id UUID, -- references render.exports
|
||||
parent_file_id UUID REFERENCES user_files(id) ON DELETE SET NULL, -- derived files
|
||||
|
||||
-- Lifecycle
|
||||
last_used_at TIMESTAMPTZ,
|
||||
use_count INT NOT NULL DEFAULT 0,
|
||||
|
||||
-- Sharing
|
||||
is_public BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
share_token TEXT,
|
||||
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_files_user_folder ON user_files(user_id, user_folder_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_files_tenant ON user_files(tenant_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_files_type ON user_files(user_id, file_type) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_files_hash ON user_files(md5_hash) WHERE md5_hash IS NOT NULL;
|
||||
CREATE INDEX idx_files_unused ON user_files(last_used_at) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_files_name_trgm ON user_files USING gin (name gin_trgm_ops);
|
||||
CREATE INDEX idx_files_share ON user_files(share_token) WHERE share_token IS NOT NULL;
|
||||
|
||||
CREATE TRIGGER tg_files_updated_at
|
||||
BEFORE UPDATE ON user_files FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- storage_quotas — current usage per user
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE storage_quotas (
|
||||
user_id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
plan_quota_bytes BIGINT NOT NULL DEFAULT 0, -- from plan
|
||||
bonus_quota_bytes BIGINT NOT NULL DEFAULT 0, -- purchased extra
|
||||
used_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
-- Cached counts
|
||||
video_count INT NOT NULL DEFAULT 0,
|
||||
image_count INT NOT NULL DEFAULT 0,
|
||||
audio_count INT NOT NULL DEFAULT 0,
|
||||
video_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
image_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
audio_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
-- Notifications
|
||||
last_90pct_notified_at TIMESTAMPTZ,
|
||||
last_100pct_notified_at TIMESTAMPTZ,
|
||||
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_quotas_tenant ON storage_quotas(tenant_id);
|
||||
|
||||
CREATE TRIGGER tg_quotas_updated_at
|
||||
BEFORE UPDATE ON storage_quotas FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- cleanup_schedules — track what's queued for auto-delete
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE cleanup_schedules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
user_id UUID,
|
||||
|
||||
entity_type cleanup_entity_type NOT NULL,
|
||||
entity_id UUID NOT NULL,
|
||||
entity_path TEXT, -- filesystem path for temp folders
|
||||
|
||||
scheduled_delete_at TIMESTAMPTZ NOT NULL,
|
||||
notify_user_at TIMESTAMPTZ, -- send "expires in 3 days" notice
|
||||
user_notified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
user_notified_at TIMESTAMPTZ,
|
||||
|
||||
status cleanup_status NOT NULL DEFAULT 'Scheduled',
|
||||
processed_at TIMESTAMPTZ,
|
||||
processing_error TEXT,
|
||||
bytes_freed BIGINT,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cleanup_due ON cleanup_schedules(scheduled_delete_at) WHERE status = 'Scheduled';
|
||||
CREATE INDEX idx_cleanup_notify_due ON cleanup_schedules(notify_user_at) WHERE user_notified = FALSE AND notify_user_at IS NOT NULL;
|
||||
CREATE INDEX idx_cleanup_entity ON cleanup_schedules(entity_type, entity_id);
|
||||
|
||||
CREATE TRIGGER tg_cleanup_updated_at
|
||||
BEFORE UPDATE ON cleanup_schedules FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- upload_sessions — multipart / chunked upload tracking
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE upload_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
|
||||
minio_bucket TEXT NOT NULL,
|
||||
minio_key TEXT NOT NULL,
|
||||
minio_upload_id TEXT NOT NULL, -- S3/MinIO multipart ID
|
||||
|
||||
filename TEXT NOT NULL,
|
||||
mime_type TEXT,
|
||||
total_size_bytes BIGINT NOT NULL,
|
||||
chunks_received INT NOT NULL DEFAULT 0,
|
||||
bytes_received BIGINT NOT NULL DEFAULT 0,
|
||||
chunk_size_bytes INT NOT NULL DEFAULT 5242880, -- 5MB default
|
||||
|
||||
target_folder_id UUID REFERENCES user_folders(id) ON DELETE SET NULL,
|
||||
target_file_id UUID, -- created when complete
|
||||
|
||||
status upload_status NOT NULL DEFAULT 'Uploading',
|
||||
error_message TEXT,
|
||||
|
||||
expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '24 hours',
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_uploads_user ON upload_sessions(user_id, created_at DESC);
|
||||
CREATE INDEX idx_uploads_expired ON upload_sessions(expires_at) WHERE status = 'Uploading';
|
||||
|
||||
CREATE TRIGGER tg_uploads_updated_at
|
||||
BEFORE UPDATE ON upload_sessions FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- minio_buckets — bucket registry (per region, per purpose)
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE minio_buckets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
region TEXT NOT NULL,
|
||||
endpoint TEXT NOT NULL,
|
||||
purpose TEXT NOT NULL, -- 'templates','user-uploads','exports','snapshots','voiceovers'
|
||||
is_public BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
cdn_base_url TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_buckets_region_purpose ON minio_buckets(region, purpose) WHERE is_active = TRUE;
|
||||
Reference in New Issue
Block a user