-- ===================================================================== -- expense_db 초기화 스크립트 (PostgreSQL) -- ===================================================================== -- 실행 방법 (운영 PostgreSQL 컨테이너명: postgres-db): -- -- 1) DB / 역할 생성 (superuser 로 1회) -- docker exec -i postgres-db psql -U postgres -v app_password='<강한비밀번호>' \ -- < scripts/sql/expense_db_init.sql -- -- 2) main-app .env 에 연결 정보 등록 -- EXPENSE_DB_URL=postgresql://expense_app:<강한비밀번호>@postgres-db:5432/expense_db -- -- 주의: -- - 기존 DB 가 있으면 DROP 하지 않는다. CREATE DATABASE 만 IF NOT EXISTS 대체. -- - 비밀번호는 절대 Git 에 커밋하지 않는다. psql -v 또는 \set 으로만 전달. -- - DROP/TRUNCATE 가 필요한 경우 본 스크립트를 수정하지 말고 별도 작업으로 진행. -- ===================================================================== \set ON_ERROR_STOP on -- DB 가 없을 때만 생성 SELECT 'CREATE DATABASE expense_db ENCODING ''UTF8'' TEMPLATE template0' WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'expense_db') \gexec -- 앱 전용 로그인 역할 -- psql 변수(:'app_password')는 dollar-quoted 블록 안에서 치환되지 않으므로 -- DO 블록을 쓰지 않고 \gexec 로 동적 SQL 을 생성·실행한다. SELECT 'CREATE ROLE expense_app LOGIN PASSWORD ' || quote_literal(:'app_password') WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'expense_app') \gexec -- 항상 최신 비밀번호로 동기화 SELECT 'ALTER ROLE expense_app WITH LOGIN PASSWORD ' || quote_literal(:'app_password') \gexec GRANT CONNECT ON DATABASE expense_db TO expense_app; -- 이제 expense_db 컨텍스트로 전환 \connect expense_db CREATE TABLE IF NOT EXISTS expense_items ( id TEXT PRIMARY KEY, owner TEXT NOT NULL, spent_at DATE NOT NULL, category TEXT NOT NULL, method TEXT NOT NULL, merchant TEXT NOT NULL DEFAULT '', amount BIGINT NOT NULL DEFAULT 0 CHECK (amount >= 0), memo TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT '작성중', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_expense_owner_spent ON expense_items (owner, spent_at DESC); CREATE INDEX IF NOT EXISTS idx_expense_status ON expense_items (status); CREATE INDEX IF NOT EXISTS idx_expense_created_at ON expense_items (created_at DESC); -- updated_at 자동 갱신 트리거 CREATE OR REPLACE FUNCTION expense_set_updated_at() RETURNS trigger AS $$ BEGIN NEW.updated_at := now(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trg_expense_set_updated_at ON expense_items; CREATE TRIGGER trg_expense_set_updated_at BEFORE UPDATE ON expense_items FOR EACH ROW EXECUTE FUNCTION expense_set_updated_at(); -- 권한 GRANT USAGE ON SCHEMA public TO expense_app; GRANT SELECT, INSERT, UPDATE, DELETE ON expense_items TO expense_app; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO expense_app; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO expense_app; -- 확인용 출력 SELECT 'expense_db ready' AS status;