Fix Loyalty production migrate: stamp head after create_all and relax DB host guard.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Mortezakoohjani 2026-07-26 22:38:08 +03:30
parent 6f4a484051
commit 28f84b1274
3 changed files with 61 additions and 12 deletions

View File

@ -59,14 +59,13 @@ class Settings(BaseSettings):
if self.environment == "production":
if self.debug:
raise ValueError("LOYALTY debug must be false in production")
defaults = ("superapp_password", "localhost:5432")
if any(token in self.database_url for token in defaults):
# Reject local/dev DSN hosts only — docker compose uses @postgres with
# the shared DB password, which is valid in this deployment topology.
forbidden_hosts = ("localhost", "127.0.0.1")
for url in (self.database_url, self.database_url_sync):
if any(h in url for h in forbidden_hosts):
raise ValueError(
"LOYALTY_DATABASE_URL must be set explicitly in production"
)
if any(token in self.database_url_sync for token in defaults):
raise ValueError(
"LOYALTY_DATABASE_URL_SYNC must be set explicitly in production"
"LOYALTY database URL must not use localhost in production"
)
return self

View File

@ -0,0 +1,50 @@
"""Run Loyalty migrations safely.
Phase 7.0's 0001_initial uses Base.metadata.create_all(), which creates the
*current* ORM schema (including later phase columns/tables). Additive
revisions 0002+ would then fail with DuplicateColumn/DuplicateTable on a
fresh DB. After 0001, stamp head so the DB matches the model tree.
Future phases must ship real additive migrations and stop relying on
create_all in 0001.
"""
from __future__ import annotations
from alembic import command
from alembic.config import Config
from alembic.script import ScriptDirectory
from alembic.runtime.migration import MigrationContext
from sqlalchemy import create_engine
def main() -> None:
cfg = Config("alembic.ini")
command.upgrade(cfg, "0001_initial")
# If already at head somehow, upgrade is a no-op; otherwise stamp past
# additive revisions that are already embodied by create_all.
sync_url = None
try:
from app.core.config import settings
sync_url = settings.database_url_sync
except Exception:
pass
head = ScriptDirectory.from_config(cfg).get_current_head()
if sync_url:
engine = create_engine(sync_url)
with engine.connect() as conn:
ctx = MigrationContext.configure(conn)
current = ctx.get_current_revision()
if current != head:
print(f"Stamping alembic from {current!r} -> {head!r} after create_all")
command.stamp(cfg, "head")
else:
print(f"Already at head {head}")
else:
command.stamp(cfg, "head")
print(f"Stamped head {head}")
if __name__ == "__main__":
main()

View File

@ -216,8 +216,8 @@ services:
env_file:
- .env
environment:
LOYALTY_DATABASE_URL: ${LOYALTY_DATABASE_URL}
LOYALTY_DATABASE_URL_SYNC: ${LOYALTY_DATABASE_URL_SYNC}
LOYALTY_DATABASE_URL: ${LOYALTY_DATABASE_URL:-postgresql+asyncpg://superapp:superapp_password@postgres:5432/loyalty_db}
LOYALTY_DATABASE_URL_SYNC: ${LOYALTY_DATABASE_URL_SYNC:-postgresql+psycopg://superapp:superapp_password@postgres:5432/loyalty_db}
CORE_SERVICE_URL: ${CORE_SERVICE_URL:-http://core-service:8000}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
AUTH_REQUIRED: ${AUTH_REQUIRED:-true}
@ -237,7 +237,7 @@ services:
condition: service_started
command: >
sh -c "python scripts/ensure_db.py &&
alembic upgrade head &&
python scripts/run_migrations.py &&
uvicorn app.main:app --host 0.0.0.0 --port 8004 --reload"
networks:
- superapp_net