From e3a9fc717c064828296eb5128a537f6926f01c02 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Fri, 11 Sep 2026 16:56:02 +0900 Subject: [PATCH] =?UTF-8?q?provider=20=EC=9E=90=EA=B2=A9=EC=A6=9D=EB=AA=85?= =?UTF-8?q?=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=83=9D=EC=84=B1=EC=9D=84=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=20=EA=B2=80=EC=82=AC=20=EC=84=A0=ED=96=89?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 앱 DB 계정에는 스키마 CREATE 권한이 없어 런타임 CREATE TABLE IF NOT EXISTS가 테이블이 이미 있어도 권한 거부로 실패한다(운영 실측). to_regclass 존재 검사를 먼저 하고, 부트스트랩 SQL(05_runtime_auth.sql)에 DDL을 추가해 신규 볼륨에서도 소유자가 테이블을 만들도록 한다. --- apps/api/app/services/provider_credentials.py | 11 +++++++++++ infra/db/init/05_runtime_auth.sql | 13 +++++++++++++ 2 files changed, 24 insertions(+) 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(),