refactor(db): introduce alembic schema baseline

This commit is contained in:
Xiaoxia AI
2026-06-21 06:48:16 +08:00
parent 4b4602a0fd
commit 0809a079c5
12 changed files with 300 additions and 184 deletions
+8 -1
View File
@@ -1,3 +1,4 @@
import os
from logging.config import fileConfig
from sqlalchemy import engine_from_config
@@ -12,6 +13,10 @@ from packages.adapters.sqlalchemy_impl.models import Base
# access to the values within the .ini file in use.
config = context.config
database_url = os.getenv("DATABASE_URL")
if database_url:
config.set_main_option("sqlalchemy.url", database_url)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
@@ -66,7 +71,9 @@ def run_migrations_online() -> None:
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
connection=connection,
target_metadata=target_metadata,
compare_type=True,
)
with context.begin_transaction():
@@ -0,0 +1,261 @@
"""Current SQLAlchemy schema baseline.
Revision ID: 001
Revises:
Create Date: 2026-06-21
This revision represents the current runtime schema defined by
packages.adapters.sqlalchemy_impl.models. Existing staging databases should be
stamped to this revision after compatibility verification; fresh databases can
run this migration normally.
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "001"
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"users",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("email", sa.String(length=255), nullable=False),
sa.Column("username", sa.String(length=100), nullable=True),
sa.Column("display_name", sa.String(length=255), nullable=False),
sa.Column("password_hash", sa.String(length=255), nullable=False),
sa.Column("email_verified", sa.Boolean(), nullable=False),
sa.Column("email_verification_token", sa.String(length=255), nullable=True),
sa.Column("password_reset_token", sa.String(length=255), nullable=True),
sa.Column("password_reset_expires_at", sa.DateTime(), nullable=True),
sa.Column("last_login_at", sa.DateTime(), nullable=True),
sa.Column("last_login_ip", sa.String(length=50), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
op.create_table(
"projects",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("name", sa.String(length=100), nullable=False),
sa.Column("description", sa.Text(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_projects_workspace_id"), "projects", ["workspace_id"], unique=False)
op.create_table(
"asset_libraries",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("workspace_id", sa.String(length=36), nullable=False),
sa.Column("project_id", sa.String(length=36), nullable=True),
sa.Column("name", sa.String(length=255), nullable=False),
sa.Column("kind", sa.String(length=20), nullable=False),
sa.Column("asset_count", sa.Float(), nullable=False),
sa.Column("total_size", sa.Float(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_asset_libraries_kind"), "asset_libraries", ["kind"], unique=False)
op.create_index(op.f("ix_asset_libraries_project_id"), "asset_libraries", ["project_id"], unique=False)
op.create_index(op.f("ix_asset_libraries_workspace_id"), "asset_libraries", ["workspace_id"], unique=False)
op.create_table(
"assets",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("workspace_id", sa.String(length=36), nullable=False),
sa.Column("project_id", sa.String(length=36), nullable=False),
sa.Column("asset_library_id", sa.String(length=36), nullable=False),
sa.Column("name", sa.String(length=500), nullable=False),
sa.Column("file_type", sa.String(length=20), nullable=False),
sa.Column("file_size", sa.Float(), nullable=False),
sa.Column("file_url", sa.String(length=1000), nullable=False),
sa.Column("thumbnail_url", sa.String(length=1000), nullable=True),
sa.Column("duration", sa.Float(), nullable=True),
sa.Column("width", sa.Float(), nullable=True),
sa.Column("height", sa.Float(), nullable=True),
sa.Column("fps", sa.Float(), nullable=True),
sa.Column("codec", sa.String(length=50), nullable=True),
sa.Column("status", sa.String(length=20), nullable=False),
sa.Column("classification_status", sa.String(length=20), nullable=False),
sa.Column("classification_result", sa.Text(), nullable=True),
sa.Column("quality_score", sa.Float(), nullable=True),
sa.Column("uploaded_by_user_id", sa.String(length=36), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_assets_asset_library_id"), "assets", ["asset_library_id"], unique=False)
op.create_index(op.f("ix_assets_classification_status"), "assets", ["classification_status"], unique=False)
op.create_index(op.f("ix_assets_created_at"), "assets", ["created_at"], unique=False)
op.create_index(op.f("ix_assets_file_type"), "assets", ["file_type"], unique=False)
op.create_index(op.f("ix_assets_project_id"), "assets", ["project_id"], unique=False)
op.create_index(op.f("ix_assets_status"), "assets", ["status"], unique=False)
op.create_index(op.f("ix_assets_workspace_id"), "assets", ["workspace_id"], unique=False)
op.create_table(
"ingest_jobs",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("project_id", sa.String(length=32), nullable=False),
sa.Column("library_id", sa.String(length=32), nullable=False),
sa.Column("storage_key", sa.String(length=255), nullable=False),
sa.Column("status", sa.String(length=20), nullable=False),
sa.Column("error_message", sa.Text(), nullable=False),
sa.Column("result_asset_id", sa.String(length=32), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_ingest_jobs_library_id"), "ingest_jobs", ["library_id"], unique=False)
op.create_index(op.f("ix_ingest_jobs_project_id"), "ingest_jobs", ["project_id"], unique=False)
op.create_index(op.f("ix_ingest_jobs_workspace_id"), "ingest_jobs", ["workspace_id"], unique=False)
op.create_table(
"classification_jobs",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("project_id", sa.String(length=32), nullable=False),
sa.Column("asset_id", sa.String(length=32), nullable=False),
sa.Column("status", sa.String(length=20), nullable=False),
sa.Column("classification", sa.String(length=50), nullable=False),
sa.Column("confidence", sa.Float(), nullable=False),
sa.Column("error_message", sa.Text(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_classification_jobs_asset_id"), "classification_jobs", ["asset_id"], unique=False)
op.create_index(op.f("ix_classification_jobs_project_id"), "classification_jobs", ["project_id"], unique=False)
op.create_index(op.f("ix_classification_jobs_workspace_id"), "classification_jobs", ["workspace_id"], unique=False)
op.create_table(
"generation_tasks",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("project_id", sa.String(length=32), nullable=False),
sa.Column("strategy_id", sa.String(length=32), nullable=False),
sa.Column("asset_library_id", sa.String(length=32), nullable=False),
sa.Column("voice_library_id", sa.String(length=32), nullable=False),
sa.Column("status", sa.String(length=20), nullable=False),
sa.Column("progress", sa.Float(), nullable=False),
sa.Column("result_count", sa.Float(), nullable=False),
sa.Column("error_message", sa.Text(), nullable=False),
sa.Column("started_at", sa.DateTime(), nullable=True),
sa.Column("completed_at", sa.DateTime(), nullable=True),
sa.Column("created_by_user_id", sa.String(length=32), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_generation_tasks_asset_library_id"), "generation_tasks", ["asset_library_id"], unique=False)
op.create_index(op.f("ix_generation_tasks_project_id"), "generation_tasks", ["project_id"], unique=False)
op.create_index(op.f("ix_generation_tasks_status"), "generation_tasks", ["status"], unique=False)
op.create_index(op.f("ix_generation_tasks_workspace_id"), "generation_tasks", ["workspace_id"], unique=False)
op.create_table(
"generated_videos",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("project_id", sa.String(length=32), nullable=False),
sa.Column("generation_task_id", sa.String(length=32), nullable=False),
sa.Column("name", sa.String(length=255), nullable=False),
sa.Column("file_url", sa.String(length=1000), nullable=False),
sa.Column("file_size", sa.Float(), nullable=False),
sa.Column("duration", sa.Float(), nullable=False),
sa.Column("thumbnail_url", sa.String(length=1000), nullable=True),
sa.Column("width", sa.Float(), nullable=False),
sa.Column("height", sa.Float(), nullable=False),
sa.Column("fps", sa.Float(), nullable=False),
sa.Column("generated_at", sa.DateTime(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_generated_videos_generation_task_id"), "generated_videos", ["generation_task_id"], unique=False)
op.create_index(op.f("ix_generated_videos_project_id"), "generated_videos", ["project_id"], unique=False)
op.create_index(op.f("ix_generated_videos_workspace_id"), "generated_videos", ["workspace_id"], unique=False)
op.create_table(
"tasks",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("project_id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("name", sa.String(length=200), nullable=False),
sa.Column("description", sa.Text(), nullable=False),
sa.Column("status", sa.String(length=20), nullable=False),
sa.Column("priority", sa.String(length=20), nullable=False),
sa.Column("parent_task_id", sa.String(length=32), nullable=False),
sa.Column("assignee_user_id", sa.String(length=32), nullable=False),
sa.Column("progress", sa.Float(), nullable=False),
sa.Column("planned_start_date", sa.DateTime(), nullable=True),
sa.Column("planned_end_date", sa.DateTime(), nullable=True),
sa.Column("actual_start_date", sa.DateTime(), nullable=True),
sa.Column("actual_end_date", sa.DateTime(), nullable=True),
sa.Column("tags_json", sa.Text(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_tasks_parent_task_id"), "tasks", ["parent_task_id"], unique=False)
op.create_index(op.f("ix_tasks_project_id"), "tasks", ["project_id"], unique=False)
op.create_index(op.f("ix_tasks_status"), "tasks", ["status"], unique=False)
op.create_index(op.f("ix_tasks_workspace_id"), "tasks", ["workspace_id"], unique=False)
op.create_table(
"milestones",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("project_id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("name", sa.String(length=200), nullable=False),
sa.Column("description", sa.Text(), nullable=False),
sa.Column("target_date", sa.DateTime(), nullable=True),
sa.Column("completed", sa.Boolean(), nullable=False),
sa.Column("completed_at", sa.DateTime(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_milestones_project_id"), "milestones", ["project_id"], unique=False)
op.create_index(op.f("ix_milestones_workspace_id"), "milestones", ["workspace_id"], unique=False)
op.create_table(
"task_issues",
sa.Column("id", sa.String(length=32), nullable=False),
sa.Column("task_id", sa.String(length=32), nullable=False),
sa.Column("project_id", sa.String(length=32), nullable=False),
sa.Column("workspace_id", sa.String(length=32), nullable=False),
sa.Column("title", sa.String(length=200), nullable=False),
sa.Column("description", sa.Text(), nullable=False),
sa.Column("resolved", sa.Boolean(), nullable=False),
sa.Column("resolved_at", sa.DateTime(), nullable=True),
sa.Column("created_by_user_id", sa.String(length=32), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_task_issues_project_id"), "task_issues", ["project_id"], unique=False)
op.create_index(op.f("ix_task_issues_task_id"), "task_issues", ["task_id"], unique=False)
op.create_index(op.f("ix_task_issues_workspace_id"), "task_issues", ["workspace_id"], unique=False)
def downgrade() -> None:
op.drop_table("task_issues")
op.drop_table("milestones")
op.drop_table("tasks")
op.drop_table("generated_videos")
op.drop_table("generation_tasks")
op.drop_table("classification_jobs")
op.drop_table("ingest_jobs")
op.drop_table("assets")
op.drop_table("asset_libraries")
op.drop_table("projects")
op.drop_table("users")
-90
View File
@@ -1,90 +0,0 @@
"""Initial schema
Revision ID: 001
Revises:
Create Date: 2026-06-15
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '001'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Create projects table
op.create_table(
'projects',
sa.Column('id', sa.String(32), nullable=False),
sa.Column('workspace_id', sa.String(32), nullable=False),
sa.Column('name', sa.String(100), nullable=False),
sa.Column('description', sa.Text(), nullable=False, server_default=''),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_projects_workspace_id'), 'projects', ['workspace_id'], unique=False)
# Create asset_libraries table
op.create_table(
'asset_libraries',
sa.Column('id', sa.String(32), nullable=False),
sa.Column('workspace_id', sa.String(32), nullable=False),
sa.Column('project_id', sa.String(32), nullable=False),
sa.Column('name', sa.String(100), nullable=False),
sa.Column('kind', sa.String(20), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_asset_libraries_workspace_id'), 'asset_libraries', ['workspace_id'], unique=False)
op.create_index(op.f('ix_asset_libraries_project_id'), 'asset_libraries', ['project_id'], unique=False)
# Create assets table
op.create_table(
'assets',
sa.Column('id', sa.String(32), nullable=False),
sa.Column('workspace_id', sa.String(32), nullable=False),
sa.Column('project_id', sa.String(32), nullable=False),
sa.Column('library_id', sa.String(32), nullable=False),
sa.Column('name', sa.String(100), nullable=False),
sa.Column('storage_key', sa.String(255), nullable=False),
sa.Column('mime_type', sa.String(100), nullable=False),
sa.Column('metadata_json', sa.Text(), nullable=False, server_default='{}'),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_assets_workspace_id'), 'assets', ['workspace_id'], unique=False)
op.create_index(op.f('ix_assets_project_id'), 'assets', ['project_id'], unique=False)
op.create_index(op.f('ix_assets_library_id'), 'assets', ['library_id'], unique=False)
# Create ingest_jobs table
op.create_table(
'ingest_jobs',
sa.Column('id', sa.String(32), nullable=False),
sa.Column('workspace_id', sa.String(32), nullable=False),
sa.Column('project_id', sa.String(32), nullable=False),
sa.Column('library_id', sa.String(32), nullable=False),
sa.Column('storage_key', sa.String(255), nullable=False),
sa.Column('status', sa.String(20), nullable=False, server_default='pending'),
sa.Column('error_message', sa.Text(), nullable=False, server_default=''),
sa.Column('result_asset_id', sa.String(32), nullable=False, server_default=''),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_ingest_jobs_workspace_id'), 'ingest_jobs', ['workspace_id'], unique=False)
op.create_index(op.f('ix_ingest_jobs_project_id'), 'ingest_jobs', ['project_id'], unique=False)
op.create_index(op.f('ix_ingest_jobs_library_id'), 'ingest_jobs', ['library_id'], unique=False)
def downgrade() -> None:
op.drop_table('ingest_jobs')
op.drop_table('assets')
op.drop_table('asset_libraries')
op.drop_table('projects')
@@ -1,91 +0,0 @@
"""Add project management tables
Revision ID: 002
Revises: 001
Create Date: 2026-06-16
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '002'
down_revision: Union[str, None] = '001'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Create tasks table
op.create_table(
'tasks',
sa.Column('id', sa.String(32), nullable=False),
sa.Column('project_id', sa.String(32), nullable=False),
sa.Column('workspace_id', sa.String(32), nullable=False),
sa.Column('name', sa.String(200), nullable=False),
sa.Column('description', sa.Text(), nullable=False, server_default=''),
sa.Column('status', sa.String(20), nullable=False, server_default='pending'),
sa.Column('priority', sa.String(20), nullable=False, server_default='medium'),
sa.Column('parent_task_id', sa.String(32), nullable=False, server_default=''),
sa.Column('assignee_user_id', sa.String(32), nullable=False, server_default=''),
sa.Column('progress', sa.Float(), nullable=False, server_default='0'),
sa.Column('planned_start_date', sa.DateTime(), nullable=True),
sa.Column('planned_end_date', sa.DateTime(), nullable=True),
sa.Column('actual_start_date', sa.DateTime(), nullable=True),
sa.Column('actual_end_date', sa.DateTime(), nullable=True),
sa.Column('tags_json', sa.Text(), nullable=False, server_default='[]'),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tasks_project_id'), 'tasks', ['project_id'], unique=False)
op.create_index(op.f('ix_tasks_workspace_id'), 'tasks', ['workspace_id'], unique=False)
op.create_index(op.f('ix_tasks_parent_task_id'), 'tasks', ['parent_task_id'], unique=False)
op.create_index(op.f('ix_tasks_status'), 'tasks', ['status'], unique=False)
# Create milestones table
op.create_table(
'milestones',
sa.Column('id', sa.String(32), nullable=False),
sa.Column('project_id', sa.String(32), nullable=False),
sa.Column('workspace_id', sa.String(32), nullable=False),
sa.Column('name', sa.String(200), nullable=False),
sa.Column('description', sa.Text(), nullable=False, server_default=''),
sa.Column('target_date', sa.DateTime(), nullable=True),
sa.Column('completed', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('completed_at', sa.DateTime(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_milestones_project_id'), 'milestones', ['project_id'], unique=False)
op.create_index(op.f('ix_milestones_workspace_id'), 'milestones', ['workspace_id'], unique=False)
# Create task_issues table
op.create_table(
'task_issues',
sa.Column('id', sa.String(32), nullable=False),
sa.Column('task_id', sa.String(32), nullable=False),
sa.Column('project_id', sa.String(32), nullable=False),
sa.Column('workspace_id', sa.String(32), nullable=False),
sa.Column('title', sa.String(200), nullable=False),
sa.Column('description', sa.Text(), nullable=False, server_default=''),
sa.Column('resolved', sa.Boolean(), nullable=False, server_default='0'),
sa.Column('resolved_at', sa.DateTime(), nullable=True),
sa.Column('created_by_user_id', sa.String(32), nullable=False, server_default=''),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_task_issues_task_id'), 'task_issues', ['task_id'], unique=False)
op.create_index(op.f('ix_task_issues_project_id'), 'task_issues', ['project_id'], unique=False)
op.create_index(op.f('ix_task_issues_workspace_id'), 'task_issues', ['workspace_id'], unique=False)
def downgrade() -> None:
op.drop_table('task_issues')
op.drop_table('milestones')
op.drop_table('tasks')
+1
View File
@@ -19,6 +19,7 @@ class Settings(BaseSettings):
DATABASE_POOL_TIMEOUT: int = 30
DATABASE_POOL_RECYCLE: int = 3600
USE_IN_MEMORY_DB: bool = False
AUTO_CREATE_SCHEMA: bool = False
REDIS_URL: str = "redis://localhost:6379/0"
REDIS_MAX_CONNECTIONS: int = 50
+2 -1
View File
@@ -13,7 +13,8 @@ engine, SessionLocal = build_session_factory(
pool_timeout=settings.DATABASE_POOL_TIMEOUT,
pool_recycle=settings.DATABASE_POOL_RECYCLE,
)
initialize_database(engine)
if settings.AUTO_CREATE_SCHEMA:
initialize_database(engine)
def get_db() -> Generator[Session, None, None]:
+1
View File
@@ -14,6 +14,7 @@ class WorkerSettings(BaseSettings):
database_max_overflow: int = 40
database_pool_timeout: int = 30
database_pool_recycle: int = 3600
auto_create_schema: bool = False
model_config = SettingsConfigDict(
env_file=".env",
+2 -1
View File
@@ -10,4 +10,5 @@ engine, SessionLocal = build_session_factory(
pool_timeout=settings.database_pool_timeout,
pool_recycle=settings.database_pool_recycle,
)
initialize_database(engine)
if settings.auto_create_schema:
initialize_database(engine)
+2
View File
@@ -8,6 +8,8 @@ RUN python -m pip install --upgrade pip setuptools wheel && \
pip install --no-cache-dir --default-timeout=120 --retries 10 -r requirements.txt
COPY apps/api /app/apps/api
COPY packages /app/packages
COPY alembic /app/alembic
COPY alembic.ini /app/alembic.ini
WORKDIR /app/apps/api
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
+20
View File
@@ -36,5 +36,25 @@ export COMPOSE_DOCKER_CLI_BUILD=0
docker compose build --pull=false api
docker compose build --pull=false worker
docker compose run --rm --no-deps api sh -c '
cd /app &&
python - <<"PY"
import os
from sqlalchemy import create_engine, text
url = os.environ["DATABASE_URL"]
engine = create_engine(url)
with engine.begin() as conn:
has_version = conn.execute(text("SELECT to_regclass('public.alembic_version')")).scalar() is not None
has_tables = conn.execute(text("SELECT COUNT(*) FROM pg_tables WHERE schemaname = 'public' AND tablename != 'alembic_version'")).scalar()
if has_tables and not has_version:
raise SystemExit("STAMP_REQUIRED")
PY
status=$?
if [ "$status" -eq 0 ]; then
alembic upgrade head
else
alembic stamp head && alembic upgrade head
fi
'
docker compose up -d api worker web
docker compose ps
+2
View File
@@ -13,5 +13,7 @@ RUN set -eux; \
pip install --no-cache-dir --default-timeout=120 --retries 10 -r requirements.txt
COPY apps/worker /app/apps/worker
COPY packages /app/packages
COPY alembic /app/alembic
COPY alembic.ini /app/alembic.ini
WORKDIR /app/apps/worker
CMD ["celery", "-A", "worker_app.celery_app.celery_app", "worker", "--loglevel=info"]
+1
View File
@@ -11,6 +11,7 @@ pydantic-settings==2.6.0
psycopg2-binary==2.9.9
psycopg[binary]==3.1.18
sqlalchemy==2.0.35
alembic==1.13.3
# 认证
pyjwt==2.9.0