Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .do/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ alerts:

ingress:
rules:
# NextAuth OAuth routes → web (must be before /api catch-all)
- component:
name: web
match:
path:
prefix: /api/auth
# API endpoints → backend
- component:
name: api
Expand Down Expand Up @@ -130,3 +136,18 @@ services:
scope: RUN_TIME
type: SECRET
value: REPLACE_WITH_API_KEY
- key: GOOGLE_CLIENT_ID
scope: RUN_TIME
type: SECRET
value: REPLACE_WITH_GOOGLE_CLIENT_ID
- key: GOOGLE_CLIENT_SECRET
scope: RUN_TIME
type: SECRET
value: REPLACE_WITH_GOOGLE_CLIENT_SECRET
- key: AUTH_SECRET
scope: RUN_TIME
type: SECRET
value: REPLACE_WITH_AUTH_SECRET
- key: AUTH_URL
scope: RUN_TIME
value: ${APP_URL}
33 changes: 32 additions & 1 deletion agent/db/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

_pool: asyncpg.Pool | None = None
_pool_lock: asyncio.Lock | None = None
_users_table_ensured: bool = False
_DEFAULT_POOL_MIN_SIZE = 2
_DEFAULT_POOL_MAX_SIZE = 10
_DEFAULT_POOL_RETRIES = 5
Expand All @@ -31,8 +32,28 @@ def _float_env(name: str, default: float, minimum: float = 0.0) -> float:
return default


async def ensure_users_table(pool: asyncpg.Pool) -> None:
"""Create the users table if it does not already exist (idempotent)."""
async with pool.acquire() as conn:
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
email TEXT UNIQUE NOT NULL,
name TEXT,
image TEXT,
approved BOOLEAN NOT NULL DEFAULT FALSE,
domain TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_login_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This index is redundant. The email column is already defined with a UNIQUE constraint on line 42, and PostgreSQL automatically creates a unique index for every unique constraint. Removing it will save storage and slightly improve write performance.

"""
)


async def get_pool() -> asyncpg.Pool:
global _pool, _pool_lock
global _pool, _pool_lock, _users_table_ensured
if _pool_lock is None:
_pool_lock = asyncio.Lock()
if _pool is not None:
Expand Down Expand Up @@ -65,6 +86,16 @@ async def get_pool() -> asyncpg.Pool:
await asyncio.sleep(retry_delay * (attempt + 1))
if _pool is None and last_error is not None:
raise last_error
if _pool is not None and not _users_table_ensured:
try:
await ensure_users_table(_pool)
_users_table_ensured = True
except Exception:
import logging

logging.getLogger(__name__).warning(
"Could not create users table — DB may not be ready yet"
)
Comment on lines +93 to +98
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Swallowing the exception makes it difficult to diagnose why the table creation failed (e.g., permission issues or syntax errors). Using logging.exception will capture the full stack trace. Also, consider moving the import logging to the top of the file for better performance and style.

Suggested change
except Exception:
import logging
logging.getLogger(__name__).warning(
"Could not create users table — DB may not be ready yet"
)
except Exception:
import logging
logging.getLogger(__name__).exception("Could not create users table")

Comment on lines +89 to +98
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

users 테이블 초기화 실패가 영구 고착될 수 있습니다.

여기서 예외를 삼키고 계속 진행하면, Line 59가 이후 호출에서 _pool을 바로 반환하므로 ensure_users_table()를 다시 시도하지 않습니다. 게다가 첫 호출이 아직 테이블을 만드는 중일 때 다른 코루틴이 이미 _pool을 받아 users를 조회할 수 있어서, /dashboard/auth/*가 간헐적으로 relation "users" does not exist로 깨질 수 있습니다.

🔧 수정 방향 예시
 async def get_pool() -> asyncpg.Pool:
     global _pool, _pool_lock, _users_table_ensured
     if _pool_lock is None:
         _pool_lock = asyncio.Lock()
-    if _pool is not None:
+    if _pool is not None and _users_table_ensured:
         return _pool
     async with _pool_lock:
-        if _pool is not None:
+        if _pool is not None and _users_table_ensured:
             return _pool
         database_url = os.environ.get("DATABASE_URL", "")
         if not database_url:
             raise RuntimeError("DATABASE_URL environment variable is required")
@@
-        if _pool is not None and not _users_table_ensured:
+        if _pool is not None and not _users_table_ensured:
             try:
                 await ensure_users_table(_pool)
                 _users_table_ensured = True
-            except Exception:
+            except Exception:
                 import logging

                 logging.getLogger(__name__).warning(
-                    "Could not create users table — DB may not be ready yet"
+                    "Could not create users table — DB may not be ready yet",
+                    exc_info=True,
                 )
     return _pool
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@agent/db/connection.py` around lines 89 - 98, The current code swallows
exceptions from ensure_users_table(_pool) and may mark the DB as ready, causing
permanent failure and race conditions; change the logic so _users_table_ensured
is only set to True after ensure_users_table returns successfully, and on
exception do not leave a usable _pool — instead log the exception details and
clean up/reset _pool (or re-raise) so subsequent calls will retry table
creation; update the block around _pool, _users_table_ensured and
ensure_users_table() to close/reset _pool on failure and include the caught
exception in the warning log.

return _pool


Expand Down
Loading
Loading