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
38 changes: 36 additions & 2 deletions apps/api/alembic.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
# Alembic configuration
[alembic]
script_location = infrastructure/db/migrations
# TODO: Configure Alembic settings
prepend_sys_path = .
version_path_separator = os

[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
26 changes: 25 additions & 1 deletion apps/api/core/config/settings.py
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# TODO: Implement configuration settings using Pydantic BaseSettings
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
import os

# Ensure the .env file from the project root is targeted
ENV_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../.env"))

class Settings(BaseSettings):
APP_NAME: str = "TicketFlow"
APP_ENV: str = "development"
DEBUG: bool = Field(default=True, alias="APP_DEBUG")
DATABASE_URL: str
SECRET_KEY: str = Field(alias="JWT_SECRET_KEY")
ALGORITHM: str = Field(default="HS256", alias="JWT_ALGORITHM")
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
CORS_ORIGINS: list[str] | str = ["*"]

model_config = SettingsConfigDict(
env_file=ENV_PATH,
env_file_encoding="utf-8",
extra="ignore"
)

settings = Settings()
5 changes: 4 additions & 1 deletion apps/api/infrastructure/db/base.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# TODO: Define declarative base for SQLAlchemy
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
pass
70 changes: 70 additions & 0 deletions apps/api/infrastructure/db/migrations/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import asyncio
import sys
import os
from logging.config import fileConfig

from sqlalchemy.ext.asyncio import async_engine_from_config
from sqlalchemy import pool
from alembic import context

# Add project root to sys.path so apps.api imports work
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")))

# pyrefly: ignore [missing-import]
from apps.api.core.config.settings import settings
# pyrefly: ignore [missing-import]
from apps.api.infrastructure.db.base import Base

config = context.config

if config.config_file_name is not None:
fileConfig(config.config_file_name)

target_metadata = Base.metadata

def get_url():
return settings.DATABASE_URL

def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode."""
url = get_url()
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)

with context.begin_transaction():
context.run_migrations()

def do_run_migrations(connection) -> None:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()

async def run_async_migrations() -> None:
"""In this scenario we need to create an Engine
and associate a connection with the context.
"""
configuration = config.get_section(config.config_ini_section)
configuration["sqlalchemy.url"] = get_url()
connectable = async_engine_from_config(
configuration,
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)

async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)

await connectable.dispose()

def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
asyncio.run(run_async_migrations())

if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
26 changes: 26 additions & 0 deletions apps/api/infrastructure/db/migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}


def upgrade() -> None:
${upgrades if upgrades else "pass"}


def downgrade() -> None:
${downgrades if downgrades else "pass"}
22 changes: 21 additions & 1 deletion apps/api/infrastructure/db/session.py
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# TODO: Configure database session maker
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from typing import AsyncGenerator
from apps.api.core.config.settings import settings

engine = create_async_engine(
settings.DATABASE_URL,
echo=settings.DEBUG,
future=True,
)

SessionLocal = async_sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
autoflush=False,
autocommit=False,
)

async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with SessionLocal() as session:
yield session
11 changes: 11 additions & 0 deletions apps/api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ version = "0.1.0"
description = "TicketFlow Backend API"
authors = ["itsjoxdev"]

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "*"
pydantic = "*"
pydantic-settings = "*"
sqlalchemy = "*"
alembic = "*"
asyncpg = "*"
python-jose = {extras = ["cryptography"], version = "*"}
passlib = {extras = ["bcrypt"], version = "*"}

# TODO: Add build dependencies
[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
2 changes: 1 addition & 1 deletion apps/api/requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Base dependencies
fastapi
pydantic
pydantic-settings
sqlalchemy
alembic
asyncpg
python-jose[cryptography]
passlib[bcrypt]
# TODO: Add additional base dependencies
Loading