-- 启用 PostGIS 扩展 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS postgis; -- 枚举类型 CREATE TYPE artifact_category AS ENUM ( 'bronze','painting_calligraphy','porcelain','jade','gold_silver', 'lacquer','textile','stone_carving','wood_carving','dunhuang','ancient_book','other' ); CREATE TYPE artifact_level AS ENUM ('level_1','level_2','level_3','general','unknown'); CREATE TYPE artifact_status AS ENUM ('at_home','away','in_transit','unknown'); CREATE TYPE location_type AS ENUM ('domestic','overseas','unknown','in_transit'); CREATE TYPE location_precision AS ENUM ('exact_room','exact_building','city','country','region'); CREATE TYPE display_status AS ENUM ('on_display','in_storage','loaned','repairing','touring','unknown'); CREATE TYPE source_type AS ENUM ('institution_feed','manual_entry','user_report','expert_verify','public_source'); CREATE TYPE publish_status AS ENUM ('draft','pending','published','archived','rejected'); CREATE TYPE tag_value_type AS ENUM ('single','multiple','boolean','text','number'); -- users CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username VARCHAR(100) UNIQUE, phone VARCHAR(30), email VARCHAR(255), password_hash VARCHAR(255), nickname VARCHAR(100), avatar_url TEXT, user_type VARCHAR(30) NOT NULL DEFAULT 'public', institution_id UUID, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- roles CREATE TABLE roles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), code VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- permissions CREATE TABLE permissions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), code VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, module VARCHAR(50) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- user_roles CREATE TABLE user_roles ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (user_id, role_id) ); -- institutions CREATE TABLE institutions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(255) NOT NULL, short_name VARCHAR(100), code VARCHAR(100) UNIQUE, institution_type VARCHAR(50) NOT NULL DEFAULT 'museum', country VARCHAR(100) NOT NULL DEFAULT '中国', province VARCHAR(100), city VARCHAR(100), address TEXT, location geography(Point, 4326), official_website TEXT, description TEXT, is_verified BOOLEAN NOT NULL DEFAULT FALSE, publish_status publish_status NOT NULL DEFAULT 'draft', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_institutions_location_gist ON institutions USING GIST(location); CREATE INDEX idx_institutions_name ON institutions(name); CREATE INDEX idx_institutions_country_city ON institutions(country, city); -- artifacts CREATE TABLE artifacts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), unified_map_id VARCHAR(50) UNIQUE, name VARCHAR(255) NOT NULL, alternative_names TEXT[] DEFAULT '{}', category artifact_category NOT NULL DEFAULT 'other', dynasty VARCHAR(100), level artifact_level NOT NULL DEFAULT 'unknown', material VARCHAR(255), dimensions VARCHAR(255), current_status artifact_status NOT NULL DEFAULT 'unknown', home_institution_id UUID REFERENCES institutions(id), summary TEXT, story_hook VARCHAR(255), persona_quote VARCHAR(255), publish_status publish_status NOT NULL DEFAULT 'draft', created_by UUID REFERENCES users(id), updated_by UUID REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_artifacts_name ON artifacts(name); CREATE INDEX idx_artifacts_category ON artifacts(category); CREATE INDEX idx_artifacts_dynasty ON artifacts(dynasty); CREATE INDEX idx_artifacts_status ON artifacts(current_status); CREATE INDEX idx_artifacts_home_institution ON artifacts(home_institution_id); CREATE INDEX idx_artifacts_publish_status ON artifacts(publish_status); -- artifact_locations CREATE TABLE artifact_locations ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), artifact_id UUID NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE, location_type location_type NOT NULL DEFAULT 'unknown', institution_id UUID REFERENCES institutions(id), precise_location geography(Point, 4326), public_location geography(Point, 4326), fuzzy_area geography(Polygon, 4326), precision location_precision NOT NULL DEFAULT 'city', floor_plan_ref VARCHAR(255), room_name VARCHAR(255), cabinet_no VARCHAR(100), display_status display_status NOT NULL DEFAULT 'unknown', source_type source_type NOT NULL DEFAULT 'manual_entry', source_description TEXT, discoverer_user_id UUID REFERENCES users(id), is_current BOOLEAN NOT NULL DEFAULT FALSE, verified_at TIMESTAMPTZ, valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_artifact_locations_artifact_id ON artifact_locations(artifact_id); CREATE INDEX idx_artifact_locations_institution_id ON artifact_locations(institution_id); CREATE INDEX idx_artifact_locations_public_location_gist ON artifact_locations USING GIST(public_location); CREATE INDEX idx_artifact_locations_precise_location_gist ON artifact_locations USING GIST(precise_location); CREATE INDEX idx_artifact_locations_fuzzy_area_gist ON artifact_locations USING GIST(fuzzy_area); CREATE INDEX idx_artifact_locations_current ON artifact_locations(artifact_id, is_current); -- tag_categories CREATE TABLE tag_categories ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), code VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, sort_order INT NOT NULL DEFAULT 0, is_system BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- tags CREATE TABLE tags ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), category_id UUID NOT NULL REFERENCES tag_categories(id), code VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, value_type tag_value_type NOT NULL DEFAULT 'single', description TEXT, color VARCHAR(20), icon VARCHAR(100), is_system BOOLEAN NOT NULL DEFAULT FALSE, is_active BOOLEAN NOT NULL DEFAULT TRUE, sort_order INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- artifact_tags CREATE TABLE artifact_tags ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), artifact_id UUID NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE, tag_id UUID NOT NULL REFERENCES tags(id), value_text TEXT, value_number NUMERIC, confidence NUMERIC(5,2), source_type source_type NOT NULL DEFAULT 'manual_entry', review_status VARCHAR(30) NOT NULL DEFAULT 'approved', created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX idx_artifact_tags_unique ON artifact_tags(artifact_id, tag_id, COALESCE(value_text, '')); CREATE INDEX idx_artifact_tags_artifact_id ON artifact_tags(artifact_id); CREATE INDEX idx_artifact_tags_tag_id ON artifact_tags(tag_id); -- digital_assets CREATE TABLE digital_assets ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), artifact_id UUID REFERENCES artifacts(id) ON DELETE CASCADE, institution_id UUID REFERENCES institutions(id), asset_type VARCHAR(50) NOT NULL DEFAULT 'image', title VARCHAR(255), url TEXT NOT NULL, thumbnail_url TEXT, mime_type VARCHAR(100), size_bytes BIGINT, copyright_owner VARCHAR(255), license_scope TEXT, sort_order INT NOT NULL DEFAULT 0, created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_digital_assets_artifact_id ON digital_assets(artifact_id); -- operation_logs CREATE TABLE operation_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), operator_id UUID REFERENCES users(id), operator_role VARCHAR(50), action VARCHAR(100) NOT NULL, target_type VARCHAR(100) NOT NULL, target_id UUID, before_data JSONB, after_data JSONB, ip_address VARCHAR(50), user_agent TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_operation_logs_operator_id ON operation_logs(operator_id); CREATE INDEX idx_operation_logs_target ON operation_logs(target_type, target_id); CREATE INDEX idx_operation_logs_created_at ON operation_logs(created_at DESC);