feat: scaffold FastAPI backend and Vue 3 frontend
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.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user