init commit. Skeleton prepared

This commit is contained in:
2025-08-08 19:48:03 +09:00
commit d58302c2c8
127 changed files with 1329 additions and 0 deletions

17
services/chat/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
COPY src ./src
COPY alembic.ini ./
COPY alembic ./alembic
COPY docker-entrypoint.sh ./
ENV PYTHONPATH=/app/src
EXPOSE 8000
CMD ["./docker-entrypoint.sh"]

35
services/chat/alembic.ini Normal file
View File

@@ -0,0 +1,35 @@
[alembic]
script_location = alembic
timezone = UTC
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers = console
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stdout,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s %(name)s %(message)s

View File

@@ -0,0 +1,55 @@
from __future__ import annotations
import os
import sys
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config, pool
# Add ./src to sys.path
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
SRC_DIR = os.path.join(BASE_DIR, "src")
if SRC_DIR not in sys.path:
sys.path.append(SRC_DIR)
from app.db.session import Base # noqa
config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
target_metadata = Base.metadata
DATABASE_URL = os.getenv("DATABASE_URL")
def run_migrations_offline() -> None:
url = DATABASE_URL
if not url:
raise RuntimeError("DATABASE_URL is not set")
context.configure(
url=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:
url = DATABASE_URL
if not url:
raise RuntimeError("DATABASE_URL is not set")
connectable = engine_from_config(
{"sqlalchemy.url": url},
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
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()

View File

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env sh
set -e
# Run migrations (no-op if no revisions yet)
alembic -c alembic.ini upgrade head || true
# Start app
exec uvicorn app.main:app --host 0.0.0.0 --port 8000

View File

@@ -0,0 +1,10 @@
fastapi
uvicorn[standard]
SQLAlchemy>=2.0
psycopg2-binary
alembic
pydantic>=2
pydantic-settings
python-dotenv
httpx>=0.27
pytest

View File

View File

View File

@@ -0,0 +1,7 @@
from fastapi import APIRouter
router = APIRouter()
@router.get("/ping")
def ping():
return {"ping": "pong"}

View File

View File

@@ -0,0 +1,9 @@
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str | None = None
class Config:
env_prefix = ""
env_file = ".env"
case_sensitive = False

View File

View File

@@ -0,0 +1,26 @@
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from typing import Generator
DEFAULT_DB_URL = "postgresql+psycopg2://postgres:postgres@postgres:5432/chat_db"
class Base(DeclarativeBase):
pass
DATABASE_URL = os.getenv("DATABASE_URL", DEFAULT_DB_URL)
engine = create_engine(
DATABASE_URL,
pool_pre_ping=True,
future=True,
)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
def get_db() -> Generator:
db = SessionLocal()
try:
yield db
finally:
db.close()

View File

@@ -0,0 +1,11 @@
from fastapi import FastAPI
from .api.routes.ping import router as ping_router
app = FastAPI(title="CHAT Service")
@app.get("/health")
def health():
return {"status": "ok", "service": "chat"}
# v1 API
app.include_router(ping_router, prefix="/v1")

View File

View File

@@ -0,0 +1 @@
from app.db.session import Base # re-export for convenience

View File

@@ -0,0 +1,5 @@
from sqlalchemy.orm import Session
class BaseRepository:
def __init__(self, db: Session):
self.db = db

View File

@@ -0,0 +1,4 @@
from pydantic import BaseModel
class Message(BaseModel):
message: str

View File

@@ -0,0 +1,5 @@
from sqlalchemy.orm import Session
class BaseService:
def __init__(self, db: Session):
self.db = db

View File

@@ -0,0 +1,10 @@
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health():
r = client.get("/health")
assert r.status_code == 200
data = r.json()
assert data.get("status") == "ok"