diff --git a/apps/api/app/services/provider_credentials.py b/apps/api/app/services/provider_credentials.py index 33b0f84..f0a4875 100644 --- a/apps/api/app/services/provider_credentials.py +++ b/apps/api/app/services/provider_credentials.py @@ -226,8 +226,19 @@ class StoredCredential: async def ensure_table() -> None: + """테이블이 없을 때만 DDL을 실행한다. + + 앱 접속 계정(vignette_app)에는 스키마 CREATE 권한이 없다. Postgres는 + ``CREATE TABLE IF NOT EXISTS``가 no-op이어도 스키마 CREATE 권한을 먼저 + 검사하므로, 부트스트랩 SQL이 이미 만든 테이블이면 검사 없이 건너뛴다. + """ pool = get_pool() async with pool.acquire() as conn: + exists = await conn.fetchval( + "SELECT to_regclass('app.admin_provider_credential') IS NOT NULL" + ) + if exists: + return await conn.execute( """ CREATE TABLE IF NOT EXISTS app.admin_provider_credential ( diff --git a/infra/db/init/05_runtime_auth.sql b/infra/db/init/05_runtime_auth.sql index e9785a9..c5ea13c 100644 --- a/infra/db/init/05_runtime_auth.sql +++ b/infra/db/init/05_runtime_auth.sql @@ -79,6 +79,19 @@ CREATE TABLE IF NOT EXISTS app.admin_engine_config ( ALTER TABLE app.admin_engine_config ADD COLUMN IF NOT EXISTS reasoning_effort TEXT; +CREATE TABLE IF NOT EXISTS app.admin_provider_credential ( + provider TEXT PRIMARY KEY, + token_encrypted TEXT NOT NULL, + token_hint TEXT NOT NULL DEFAULT '', + auth_kind TEXT NOT NULL DEFAULT 'api_key' + CHECK (auth_kind IN ('api_key','oauth_token')), + updated_by TEXT, + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + last_verified_at TIMESTAMPTZ, + last_verify_ok BOOLEAN, + last_verify_error TEXT +); + CREATE TABLE IF NOT EXISTS app.admin_health_event ( id BIGSERIAL PRIMARY KEY, observed_at TIMESTAMPTZ NOT NULL DEFAULT now(),