5128e1551c
Backend (backend/):
- FastAPI app with layered app/{api,core,crud,db,models,schemas} structure
- TiDB connection via SQLAlchemy. The URL is built with URL.create rather
than string formatting: the password contains '@', which would otherwise be
parsed as the user/host separator and silently truncate the credential.
- Alembic environment wired to Base.metadata and the app settings, so
backend/.env stays the single source of truth for credentials
- /api/health endpoint reporting database reachability
- models/ and crud/ are intentionally empty: no model classes are defined and
no tables are created, at import time or otherwise
Frontend (frontend/):
- Vue 3 + Vite + TypeScript scaffold (create-vue, --bare)
- axios instance with a normalized error shape
- vue-router with a home route and a catch-all 404
- pinia + pinia-plugin-persistedstate; the app store persists selected keys
- Element Plus with its icons registered globally
- dev proxy forwards /api to the FastAPI service on port 8000
The database paper_doc was created in TiDB out of band; no table exists yet.
76 lines
2.2 KiB
Python
76 lines
2.2 KiB
Python
"""Alembic migration environment.
|
|
|
|
The connection URL comes from the application settings rather than from
|
|
``alembic.ini``, so ``backend/.env`` stays the single source of truth. The URL
|
|
is passed to SQLAlchemy as a :class:`~sqlalchemy.engine.URL` object: rendering
|
|
it back to a string would re-introduce the escaping problem around the ``@``
|
|
in the TiDB password.
|
|
|
|
``target_metadata`` is wired to :class:`app.db.base.Base`, so
|
|
``alembic revision --autogenerate`` will pick up models as soon as they are
|
|
defined and imported by ``app.models``. Today that package is empty, so an
|
|
autogenerated revision is expected to contain no operations.
|
|
"""
|
|
|
|
from logging.config import fileConfig
|
|
|
|
from alembic import context
|
|
from sqlalchemy import create_engine, pool
|
|
|
|
from app.core.config import get_settings
|
|
from app.db.base import Base
|
|
|
|
# Import the models package so that every model module is registered on
|
|
# Base.metadata before autogenerate inspects it. The package defines no models
|
|
# yet; keeping the import here means the first model added is picked up with no
|
|
# further change to this file.
|
|
from app import models # noqa: F401
|
|
|
|
config = context.config
|
|
|
|
if config.config_file_name is not None:
|
|
fileConfig(config.config_file_name)
|
|
|
|
target_metadata = Base.metadata
|
|
|
|
settings = get_settings()
|
|
|
|
|
|
def run_migrations_offline() -> None:
|
|
"""Emit SQL to stdout without connecting to the database."""
|
|
context.configure(
|
|
url=settings.sqlalchemy_url,
|
|
target_metadata=target_metadata,
|
|
literal_binds=True,
|
|
dialect_opts={"paramstyle": "named"},
|
|
compare_type=True,
|
|
)
|
|
|
|
with context.begin_transaction():
|
|
context.run_migrations()
|
|
|
|
|
|
def run_migrations_online() -> None:
|
|
"""Connect to TiDB and apply migrations."""
|
|
connectable = create_engine(
|
|
settings.sqlalchemy_url,
|
|
poolclass=pool.NullPool,
|
|
future=True,
|
|
)
|
|
|
|
with connectable.connect() as connection:
|
|
context.configure(
|
|
connection=connection,
|
|
target_metadata=target_metadata,
|
|
compare_type=True,
|
|
)
|
|
|
|
with context.begin_transaction():
|
|
context.run_migrations()
|
|
|
|
|
|
if context.is_offline_mode():
|
|
run_migrations_offline()
|
|
else:
|
|
run_migrations_online()
|