initial commit

This commit is contained in:
Sosokker 2025-06-24 09:41:02 +07:00
commit 9985ba430d
19 changed files with 2052 additions and 0 deletions

19
.env.example Normal file
View File

@ -0,0 +1,19 @@
# API Configuration
API_PORT=8001
# security
SECRET_KEY=your-secret-key-here
# LLM
OPENAI_API_KEY=your-openai-key
# Database Configuration
DB_PORT=5432
POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_DB=mydatabase
# Environment
ENVIRONMENT=production
DEBUG=False

108
.gitignore vendored Normal file
View File

@ -0,0 +1,108 @@
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.idea/*

View File

@ -0,0 +1,84 @@
### 🛠️ Project Tech & Environment Rules
* **Python Version:** `3.12`
* **Dependency Management:** [`uv`](https://github.com/astral-sh/uv) (fast, deterministic, PEP 582-compatible)
* **Backend Framework:** [`FastAPI`](https://fastapi.tiangolo.com/)
* **ORM:** [`SQLAlchemy 2.x`](https://docs.sqlalchemy.org/en/20/)
* **Migrations:** [`Alembic`](https://alembic.sqlalchemy.org/)
* **Authentication:** [`fastapi-users`](https://fastapi-users.github.io/fastapi-users/latest/) + [`fastapi-jwt-auth`](https://indominusbyte.github.io/fastapi-jwt-auth/)
* **Rate Limiting:** [`fastapi-limiter`](https://github.com/long2ice/fastapi-limiter)
* **Caching:** [`fastapi-cache`](https://github.com/long2ice/fastapi-cache)
* **Email Service:** [`fastapi-mail`](https://github.com/sabuhish/fastapi-mail)
* **Pagination:** [`fastapi-pagination`](https://github.com/uriyyo/fastapi-pagination)
* **LLM Layer:** [`litellm`](https://github.com/BerriAI/litellm)
* **Embedding Models:** [`Hugging Face Transformers`](https://huggingface.co/models)
* **Vector Store:** `pgvector` with PostgreSQL
Use pure dataclasses between in-app layers (as a transport objects). Use pydantic to validate user (external) input like web APIs.
---
### 🧑‍💻 Python & Backend Code Quality Rules
#### 📦 Structure & Conventions
* **Follow modern `SQLAlchemy 2.0` best practices** (use `async engine`, `DeclarativeBase`, `SessionLocal()` pattern).
* **Separate concerns clearly:**
* `models/`: SQLAlchemy models
* `schemas/`: Pydantic models
* `api/routes/`: FastAPI routers
* `services/`: Business logic
* `core/`: Settings, config, and utilities
* `tests/`: Test suite
#### 🧹 Clean Code Principles
1. **Use Meaningful Names:** Functions, classes, variables, and routes should clearly communicate their intent.
2. **Avoid Overengineering:** YAGNI (You Arent Gonna Need It) — keep your code minimal, testable, and readable.
3. **Follow PEP 8 + Black Formatting:** Auto-format with `ruff`, lint with `ruff` or `flake8`.
4. **Use Type Hints Everywhere:** Both function arguments and return types must use type annotations.
5. **Use Docstrings:**
* One-liner for simple functions.
* Full docstring for public APIs and complex logic.
6. **Write Isolated, Testable Logic:** Favor pure functions where possible, especially in `services/`.
7. **Handle Exceptions Gracefully:**
* Use `HTTPException` for expected FastAPI errors.
* Log unexpected errors using `structlog`.
8. **Use Dependency Injection:** Use `Depends()` for shared logic (e.g., current user, DB session, rate limiter).
---
### 🧪 Testing Rules
* Use `pytest` as your testing framework.
* Coverage should include:
* CRUD operations
* API endpoints
* Embedding & RAG pipeline logic
* Use `pytest-asyncio` for async route testing.
* Use fixtures for test data setup.
---
### 🔒 Security Practices
* Never store plaintext passwords — use hashing (`argon2`, `bcrypt` via `fastapi-users`).
* Sanitize file uploads & inputs — protect against injection.
* Use CORS middleware correctly (`allow_credentials`, `allow_methods`, etc.).
* Enable rate limiting on sensitive routes like login & upload.
---
### 🚀 Performance & Observability
* Add `structlog` structured logging to:
* API entry/exit points
* Query vector lookup latency
* LLM response times
* Cache results where appropriate (`fastapi-cache`) — especially static vector responses.
* Stream LLM responses via FastAPI's `StreamingResponse`.

View File

@ -0,0 +1,88 @@
---
trigger: always_on
---
### 🛠️ Project Tech & Environment Rules
* **Python Version:** `3.12`
* **Dependency Management:** [`uv`](https://github.com/astral-sh/uv) (fast, deterministic, PEP 582-compatible)
* **Backend Framework:** [`FastAPI`](https://fastapi.tiangolo.com/)
* **ORM:** [`SQLAlchemy 2.x`](https://docs.sqlalchemy.org/en/20/)
* **Migrations:** [`Alembic`](https://alembic.sqlalchemy.org/)
* **Authentication:** [`fastapi-users`](https://fastapi-users.github.io/fastapi-users/latest/) + [`fastapi-jwt-auth`](https://indominusbyte.github.io/fastapi-jwt-auth/)
* **Rate Limiting:** [`fastapi-limiter`](https://github.com/long2ice/fastapi-limiter)
* **Caching:** [`fastapi-cache`](https://github.com/long2ice/fastapi-cache)
* **Email Service:** [`fastapi-mail`](https://github.com/sabuhish/fastapi-mail)
* **Pagination:** [`fastapi-pagination`](https://github.com/uriyyo/fastapi-pagination)
* **LLM Layer:** [`litellm`](https://github.com/BerriAI/litellm)
* **Embedding Models:** [`Hugging Face Transformers`](https://huggingface.co/models)
* **Vector Store:** `pgvector` with PostgreSQL
Use pure dataclasses between in-app layers (as a transport objects). Use pydantic to validate user (external) input like web APIs.
---
### 🧑‍💻 Python & Backend Code Quality Rules
#### 📦 Structure & Conventions
* **Follow modern `SQLAlchemy 2.0` best practices** (use `async engine`, `DeclarativeBase`, `SessionLocal()` pattern).
* **Separate concerns clearly:**
* `models/`: SQLAlchemy models
* `schemas/`: Pydantic models
* `api/routes/`: FastAPI routers
* `services/`: Business logic
* `core/`: Settings, config, and utilities
* `tests/`: Test suite
#### 🧹 Clean Code Principles
1. **Use Meaningful Names:** Functions, classes, variables, and routes should clearly communicate their intent.
2. **Avoid Overengineering:** YAGNI (You Arent Gonna Need It) — keep your code minimal, testable, and readable.
3. **Follow PEP 8 + Black Formatting:** Auto-format with `ruff`, lint with `ruff` or `flake8`.
4. **Use Type Hints Everywhere:** Both function arguments and return types must use type annotations.
5. **Use Docstrings:**
* One-liner for simple functions.
* Full docstring for public APIs and complex logic.
6. **Write Isolated, Testable Logic:** Favor pure functions where possible, especially in `services/`.
7. **Handle Exceptions Gracefully:**
* Use `HTTPException` for expected FastAPI errors.
* Log unexpected errors using `structlog`.
8. **Use Dependency Injection:** Use `Depends()` for shared logic (e.g., current user, DB session, rate limiter).
---
### 🧪 Testing Rules
* Use `pytest` as your testing framework.
* Coverage should include:
* CRUD operations
* API endpoints
* Embedding & RAG pipeline logic
* Use `pytest-asyncio` for async route testing.
* Use fixtures for test data setup.
---
### 🔒 Security Practices
* Never store plaintext passwords — use hashing (`argon2`, `bcrypt` via `fastapi-users`).
* Sanitize file uploads & inputs — protect against injection.
* Use CORS middleware correctly (`allow_credentials`, `allow_methods`, etc.).
* Enable rate limiting on sensitive routes like login & upload.
---
### 🚀 Performance & Observability
* Add `structlog` structured logging to:
* API entry/exit points
* Query vector lookup latency
* LLM response times
* Cache results where appropriate (`fastapi-cache`) — especially static vector responses.
* Stream LLM responses via FastAPI's `StreamingResponse`.

36
Dockerfile Normal file
View File

@ -0,0 +1,36 @@
# Start with the official Python 3.12 slim-buster image.
# slim-buster is a good choice for production as it's smaller.
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
# Install the project into `/app`
WORKDIR /app
# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1
# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy
# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --no-dev
# Then, add the rest of the project source code and install it
# Installing separately from its dependencies allows optimal layer caching
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/Lib:$PATH"
# Reset the entrypoint, don't invoke `uv`
ENTRYPOINT []
# Command to run the application
# Use uvicorn to run the FastAPI application.
# --host 0.0.0.0 is required to make the app accessible from outside the container
# --port 8000 to match the exposed port
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

0
Makefile Normal file
View File

0
README.md Normal file
View File

0
app/__init__.py Normal file
View File

0
app/api/__init__.py Normal file
View File

View File

0
app/core/__init__.py Normal file
View File

99
app/core/config.py Normal file
View File

@ -0,0 +1,99 @@
import os
import secrets
from functools import lru_cache
from pathlib import Path
from pydantic import AnyHttpUrl, PostgresDsn, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
ROOT = Path(__file__).resolve().parent.parent.parent
ENV_FILE = ROOT / ".env"
class Settings(BaseSettings):
# Project
PROJECT_NAME: str = "Chat Hub"
VERSION: str = "0.1.0"
API_V1_STR: str = "/api/v1"
SECRET_KEY: str = secrets.token_urlsafe(32)
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 days
# Security
ALGORITHM: str = "HS256"
# Backend Server
BACKEND_CORS_ORIGINS: list[str | AnyHttpUrl] = []
@field_validator("BACKEND_CORS_ORIGINS", mode="before")
def assemble_cors_origins(self, v: str | list[str]) -> list[str] | str:
if isinstance(v, str) and not v.startswith("["):
return [i.strip() for i in v.split(",")]
if isinstance(v, (list, str)):
return v
raise ValueError(v)
# Database
POSTGRES_SERVER: str = "localhost"
POSTGRES_USER: str = "postgres"
POSTGRES_PASSWORD: str = ""
POSTGRES_DB: str = "chat_hub"
POSTGRES_PORT: str = "5432"
DATABASE_URI: PostgresDsn | None = None
@model_validator(mode="after")
def assemble_db_connection(self) -> "Settings":
if self.DATABASE_URI is None:
self.DATABASE_URI = PostgresDsn.build(
scheme="postgresql+asyncpg",
username=self.POSTGRES_USER,
password=self.POSTGRES_PASSWORD,
host=self.POSTGRES_SERVER,
port=int(self.POSTGRES_PORT),
path=f"/{self.POSTGRES_DB or ''}",
)
return self
# LLM Configuration
OPENAI_API_KEY: str | None = None
ANTHROPIC_API_KEY: str | None = None
# Vector Store
VECTOR_STORE_TYPE: str = "pgvector" # or "chroma", "faiss", etc.
EMBEDDING_MODEL: str = "sentence-transformers/all-mpnet-base-v2"
# Logging
LOG_LEVEL: str = "INFO"
# Environment
ENVIRONMENT: str = "development"
DEBUG: bool = False
TESTING: bool = False
# Rate Limiting
RATE_LIMIT: str = "100/minute"
# Caching
CACHE_TTL: int = 300 # 5 minutes
model_config = SettingsConfigDict(
env_file=ENV_FILE,
env_file_encoding="utf-8",
case_sensitive=True,
extra="ignore",
)
@lru_cache
def get_settings() -> Settings:
"""
Get cached settings instance.
This function uses lru_cache to prevent re-reading the environment on each call.
"""
return Settings()
# Create settings instance
settings = get_settings()
# Set environment variables for third-party libraries
os.environ["TOKENIZERS_PARALLELISM"] = "false"

0
app/core/enums.py Normal file
View File

0
app/core/interfaces.py Normal file
View File

83
app/main.py Normal file
View File

@ -0,0 +1,83 @@
import os
from contextlib import asynccontextmanager
from typing import Any
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
# Load environment variables
load_dotenv()
# Application metadata
APP_NAME = "Chat Hub API"
API_VERSION = "0.1.0"
APP_DESCRIPTION = """
Chat Hub API - A modern chat application with AI capabilities.
"""
# Application lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Initialize resources
print("Starting application...")
yield
# Shutdown: Clean up resources
print("Shutting down application...")
# Initialize FastAPI application
app = FastAPI(
title=APP_NAME,
description=APP_DESCRIPTION,
version=API_VERSION,
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json",
lifespan=lifespan,
)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, replace with specific origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Health check endpoint
@app.get("/health", response_model=dict[str, Any])
async def health_check() -> dict[str, Any]:
"""
Health check endpoint to verify the API is running.
"""
return {"status": "healthy", "service": APP_NAME, "version": API_VERSION}
# Root endpoint
@app.get("/")
async def root():
"""
Root endpoint that provides basic information about the API.
"""
return {
"message": f"Welcome to {APP_NAME}",
"version": API_VERSION,
"docs": "/docs",
"health_check": "/health",
}
# This allows running the app with: uvicorn main:app --reload
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("API_PORT", 8001))
uvicorn.run(
"main:app", host="0.0.0.0", port=port, reload=True, reload_dirs=["./app"]
)

View File

32
docker-compose.yml Normal file
View File

@ -0,0 +1,32 @@
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "${API_PORT:-8001}:8000"
volumes:
- ./app:/app
env_file:
- .env
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
- db
db:
image: pgvector/pgvector:pg17
env_file:
- .env
environment:
- POSTGRES_USER=${POSTGRES_USER:-user}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
- POSTGRES_DB=${POSTGRES_DB:-mydatabase}
volumes:
- db_data:/var/lib/postgresql/data
ports:
- "${DB_PORT:-5432}:5432"
volumes:
db_data:

14
pyproject.toml Normal file
View File

@ -0,0 +1,14 @@
[project]
name = "chat-hub"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi[standard]>=0.115.13",
"litellm>=1.73.0",
"pydantic-settings>=2.10.0",
"structlog>=25.4.0",
"transformers>=4.52.4",
"uvicorn>=0.34.3",
]

1489
uv.lock Normal file

File diff suppressed because it is too large Load Diff