-- +goose Up -- pending_uploads tracks short-lived presigned-URL upload sessions for direct -- client-to-B2 uploads. A row is created when the client requests a presigned -- POST policy, and either claimed (linked to a task_completion_image or -- document_image) or reaped by the cleanup worker after expiry. CREATE TABLE pending_uploads ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES auth_user(id) ON DELETE CASCADE, category VARCHAR(32) NOT NULL, b2_key VARCHAR(255) NOT NULL UNIQUE, content_type VARCHAR(127) NOT NULL, expected_bytes BIGINT NOT NULL, actual_bytes BIGINT, claimed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL ); -- Quota lookups: SUM/COUNT by user, ordered by recency. CREATE INDEX idx_pending_uploads_user_created ON pending_uploads (user_id, created_at DESC); -- Cleanup worker scan: only unclaimed expired rows. Partial index keeps it tiny. CREATE INDEX idx_pending_uploads_cleanup ON pending_uploads (expires_at) WHERE claimed_at IS NULL; -- task_completion_image and document_image gain an optional FK to the -- pending_uploads row that produced them. Nullable so legacy rows (uploaded -- through the multipart path) keep working. ALTER TABLE task_taskcompletionimage ADD COLUMN pending_upload_id BIGINT REFERENCES pending_uploads(id) ON DELETE SET NULL; ALTER TABLE task_documentimage ADD COLUMN pending_upload_id BIGINT REFERENCES pending_uploads(id) ON DELETE SET NULL; -- +goose Down ALTER TABLE task_documentimage DROP COLUMN IF EXISTS pending_upload_id; ALTER TABLE task_taskcompletionimage DROP COLUMN IF EXISTS pending_upload_id; DROP TABLE IF EXISTS pending_uploads;