Initial public release

Aurum — a self-hosted personal finance operating system: cash flow,
net worth tracking, budgets, goals, recurring payments, reports,
proactive alerts, and a compound-interest returns calculator, all
bilingual (RU/EN) and mobile-first. Ships as a three-container Docker
Compose stack (Postgres + FastAPI + nginx-served React frontend).

See README.md for setup and CONTRIBUTING.md for development.
This commit is contained in:
ZProger
2026-08-16 18:39:52 +03:00
commit fe9b079c12
199 changed files with 14410 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
from datetime import date
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_session
from app.models.budget import Budget
from app.schemas.budget import BudgetCreate, BudgetRead, BudgetStatusResponse, BudgetUpdate
from app.services.budget_service import create_budget, delete_budget, get_budget_status, list_budgets, update_budget
router = APIRouter(prefix="/budgets", tags=["budgets"])
def _to_read(budget: Budget) -> BudgetRead:
return BudgetRead(
id=budget.id,
category_id=budget.category_id,
category_name=budget.category.name,
category_color=budget.category.color,
category_icon=budget.category.icon,
monthly_limit=budget.monthly_limit,
)
@router.get("", response_model=list[BudgetRead])
async def read_budgets(session: AsyncSession = Depends(get_session)) -> list[BudgetRead]:
return [_to_read(budget) for budget in await list_budgets(session)]
@router.get("/status", response_model=BudgetStatusResponse)
async def read_budget_status(
year: int = Query(default_factory=lambda: date.today().year, ge=2000, le=2100),
month: int = Query(default_factory=lambda: date.today().month, ge=1, le=12),
session: AsyncSession = Depends(get_session),
) -> BudgetStatusResponse:
return await get_budget_status(session, year, month)
@router.post("", response_model=BudgetRead, status_code=201)
async def create_budget_route(payload: BudgetCreate, session: AsyncSession = Depends(get_session)) -> BudgetRead:
return _to_read(await create_budget(session, payload))
@router.patch("/{budget_id}", response_model=BudgetRead)
async def update_budget_route(
budget_id: int, payload: BudgetUpdate, session: AsyncSession = Depends(get_session)
) -> BudgetRead:
return _to_read(await update_budget(session, budget_id, payload))
@router.delete("/{budget_id}", status_code=204)
async def delete_budget_route(budget_id: int, session: AsyncSession = Depends(get_session)) -> None:
await delete_budget(session, budget_id)