v1.10.88 품질·보안 개선과 미디어 카탈로그 database 전환을 반영한다.

세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-22 11:05:40 +09:00
parent 9bc0f0a90e
commit de767095ba
116 changed files with 8128 additions and 2933 deletions

View File

@@ -0,0 +1,9 @@
ALTER TABLE users
ADD COLUMN IF NOT EXISTS session_version INTEGER NOT NULL DEFAULT 1;
ALTER TABLE users
DROP CONSTRAINT IF EXISTS users_session_version_check;
ALTER TABLE users
ADD CONSTRAINT users_session_version_check
CHECK (session_version > 0);

View File

@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS csp_violation_aggregates (
policy_version TEXT NOT NULL,
document_path TEXT NOT NULL,
effective_directive TEXT NOT NULL,
blocked_origin TEXT NOT NULL,
user_agent_family TEXT NOT NULL,
violation_count BIGINT NOT NULL DEFAULT 1,
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (
policy_version,
document_path,
effective_directive,
blocked_origin,
user_agent_family
),
CONSTRAINT csp_violation_aggregates_count_check
CHECK (violation_count > 0)
);
CREATE INDEX IF NOT EXISTS csp_violation_aggregates_last_seen_idx
ON csp_violation_aggregates (last_seen_at DESC);

View File

@@ -0,0 +1,148 @@
ALTER TABLE media_metadata
ADD COLUMN IF NOT EXISTS id BIGINT GENERATED BY DEFAULT AS IDENTITY,
ADD COLUMN IF NOT EXISTS file_name TEXT,
ADD COLUMN IF NOT EXISTS storage_key TEXT,
ADD COLUMN IF NOT EXISTS extension TEXT,
ADD COLUMN IF NOT EXISTS kind TEXT,
ADD COLUMN IF NOT EXISTS role TEXT,
ADD COLUMN IF NOT EXISTS size_bytes BIGINT,
ADD COLUMN IF NOT EXISTS modified_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS parent_id BIGINT,
ADD COLUMN IF NOT EXISTS origin TEXT,
ADD COLUMN IF NOT EXISTS status TEXT,
ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS missing_since TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS error_detail TEXT;
UPDATE media_metadata
SET
file_name = COALESCE(file_name, regexp_replace(url, '^.*/', '')),
storage_key = COALESCE(storage_key, regexp_replace(url, '^/uploads/', '')),
extension = COALESCE(extension, lower(substring(url from '\.[^.?#/]+$'))),
kind = COALESCE(kind, 'file'),
role = COALESCE(role, 'original'),
origin = COALESCE(origin, 'external'),
status = COALESCE(status, 'staging')
WHERE file_name IS NULL
OR storage_key IS NULL
OR kind IS NULL
OR role IS NULL
OR origin IS NULL
OR status IS NULL;
ALTER TABLE media_metadata
ALTER COLUMN file_name SET NOT NULL,
ALTER COLUMN storage_key SET NOT NULL,
ALTER COLUMN kind SET NOT NULL,
ALTER COLUMN role SET NOT NULL,
ALTER COLUMN origin SET NOT NULL,
ALTER COLUMN status SET NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS media_metadata_id_unique_idx
ON media_metadata (id);
CREATE UNIQUE INDEX IF NOT EXISTS media_metadata_storage_key_unique_idx
ON media_metadata (storage_key);
CREATE INDEX IF NOT EXISTS media_metadata_status_updated_idx
ON media_metadata (status, modified_at DESC);
CREATE INDEX IF NOT EXISTS media_metadata_kind_role_idx
ON media_metadata (kind, role);
CREATE INDEX IF NOT EXISTS media_metadata_parent_idx
ON media_metadata (parent_id)
WHERE parent_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS media_metadata_last_seen_idx
ON media_metadata (last_seen_at DESC);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'media_metadata_kind_check'
) THEN
ALTER TABLE media_metadata
ADD CONSTRAINT media_metadata_kind_check
CHECK (kind IN ('image', 'video', 'audio', 'file'));
END IF;
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'media_metadata_status_check'
) THEN
ALTER TABLE media_metadata
ADD CONSTRAINT media_metadata_status_check
CHECK (status IN ('staging', 'active', 'missing', 'deleting', 'error'));
END IF;
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'media_metadata_origin_check'
) THEN
ALTER TABLE media_metadata
ADD CONSTRAINT media_metadata_origin_check
CHECK (origin IN ('upload', 'import', 'external', 'derived', 'system'));
END IF;
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'media_metadata_parent_fk'
) THEN
ALTER TABLE media_metadata
ADD CONSTRAINT media_metadata_parent_fk
FOREIGN KEY (parent_id)
REFERENCES media_metadata (id)
ON DELETE SET NULL;
END IF;
END
$$;
CREATE TABLE IF NOT EXISTS media_references (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
media_id BIGINT REFERENCES media_metadata (id) ON DELETE CASCADE,
media_url TEXT NOT NULL,
reference_type TEXT NOT NULL,
reference_id TEXT NOT NULL,
reference_field TEXT NOT NULL,
reference_title TEXT NOT NULL DEFAULT '',
reference_slug TEXT NOT NULL DEFAULT '',
reference_status TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT media_references_target_unique
UNIQUE (media_url, reference_type, reference_id, reference_field)
);
CREATE INDEX IF NOT EXISTS media_references_media_id_idx
ON media_references (media_id)
WHERE media_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS media_references_url_idx
ON media_references (media_url);
CREATE INDEX IF NOT EXISTS media_references_target_idx
ON media_references (reference_type, reference_id);
CREATE TABLE IF NOT EXISTS media_catalog_state (
singleton BOOLEAN PRIMARY KEY DEFAULT true,
ready BOOLEAN NOT NULL DEFAULT false,
reconciled_at TIMESTAMPTZ,
filesystem_count BIGINT NOT NULL DEFAULT 0,
filesystem_bytes BIGINT NOT NULL DEFAULT 0,
catalog_count BIGINT NOT NULL DEFAULT 0,
catalog_bytes BIGINT NOT NULL DEFAULT 0,
error_detail TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT media_catalog_state_singleton_check
CHECK (singleton = true)
);
INSERT INTO media_catalog_state (singleton, ready)
VALUES (true, false)
ON CONFLICT (singleton) DO NOTHING;