Add authenticated finance REST API
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
"""Transport schemas only; business validation lives in the shared services."""
|
||||
from decimal import Decimal
|
||||
from typing import Annotated, Literal
|
||||
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, StrictBool, model_validator
|
||||
from models import cents, valid_date
|
||||
|
||||
AssetType = Literal['stock', 'etf', 'bond', 'crypto', 'interest', 'other']
|
||||
Category = Literal['dividend', 'interest', 'distribution', 'other']
|
||||
Identifier = Annotated[int, Field(strict=True, ge=1, le=9223372036854775807)]
|
||||
MoneyString = Annotated[str, Field(pattern=r'^-?\d+\.\d{2}$', examples=['0.04'])]
|
||||
|
||||
|
||||
def parse_amount(value):
|
||||
if not isinstance(value, (str, Decimal)):
|
||||
raise ValueError('Betrag als Dezimalstring senden, zum Beispiel "0.04".')
|
||||
return Decimal(cents(value)) / 100
|
||||
|
||||
|
||||
Amount = Annotated[Decimal, BeforeValidator(parse_amount, json_schema_input_type=str)]
|
||||
Day = Annotated[str, BeforeValidator(valid_date)]
|
||||
|
||||
|
||||
class RequestModel(BaseModel):
|
||||
model_config = ConfigDict(extra='forbid')
|
||||
|
||||
|
||||
class PatchModel(RequestModel):
|
||||
@model_validator(mode='before')
|
||||
@classmethod
|
||||
def reject_required_nulls(cls, data):
|
||||
if isinstance(data, dict):
|
||||
for name, value in data.items():
|
||||
if value is None and name not in {'note', 'ticker'}:
|
||||
raise ValueError('Nur Notiz und Ticker dürfen null sein.')
|
||||
return data
|
||||
|
||||
|
||||
class AssetCreate(RequestModel):
|
||||
name: str = Field(min_length=1, max_length=150)
|
||||
ticker: str | None = Field(default=None, max_length=30)
|
||||
asset_type: AssetType
|
||||
active: StrictBool = True
|
||||
|
||||
|
||||
class AssetPatch(PatchModel):
|
||||
name: str | None = Field(default=None, min_length=1, max_length=150)
|
||||
ticker: str | None = Field(default=None, max_length=30)
|
||||
asset_type: AssetType | None = None
|
||||
active: StrictBool | None = None
|
||||
|
||||
|
||||
class AssetResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
ticker: str | None
|
||||
asset_type: AssetType
|
||||
active: bool
|
||||
created_at: str
|
||||
|
||||
|
||||
class IncomeCreate(RequestModel):
|
||||
date: Day
|
||||
asset_id: Identifier
|
||||
category: Category
|
||||
amount: Amount
|
||||
note: str | None = Field(default=None, max_length=2000)
|
||||
expected: StrictBool = False
|
||||
received: StrictBool = True
|
||||
|
||||
|
||||
class IncomePatch(PatchModel):
|
||||
date: Day | None = None
|
||||
asset_id: Identifier | None = None
|
||||
category: Category | None = None
|
||||
amount: Amount | None = None
|
||||
note: str | None = Field(default=None, max_length=2000)
|
||||
expected: StrictBool | None = None
|
||||
received: StrictBool | None = None
|
||||
|
||||
|
||||
class IncomeResponse(BaseModel):
|
||||
id: int
|
||||
date: str
|
||||
asset: str = Field(description='Normalisierter Positionsname')
|
||||
asset_id: int
|
||||
category: Category
|
||||
amount: MoneyString
|
||||
note: str | None
|
||||
expected: bool
|
||||
received: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class SummaryResponse(BaseModel):
|
||||
current_month: MoneyString
|
||||
current_month_previous_year: MoneyString
|
||||
current_month_yoy_percent: MoneyString | None
|
||||
current_year: MoneyString
|
||||
previous_year: MoneyString
|
||||
current_year_yoy_percent: MoneyString | None
|
||||
all_time: MoneyString
|
||||
current_year_payment_count: int
|
||||
|
||||
|
||||
class MonthResponse(BaseModel):
|
||||
month: int
|
||||
amount: MoneyString
|
||||
|
||||
|
||||
class MonthlyResponse(BaseModel):
|
||||
year: int
|
||||
months: list[MonthResponse]
|
||||
total: MoneyString
|
||||
|
||||
|
||||
class AssetShareResponse(BaseModel):
|
||||
asset_id: int
|
||||
asset: str
|
||||
amount: MoneyString
|
||||
percentage: MoneyString | None
|
||||
|
||||
|
||||
class CategoryShareResponse(BaseModel):
|
||||
category: Category
|
||||
amount: MoneyString
|
||||
percentage: MoneyString | None
|
||||
|
||||
|
||||
class MetaResponse(BaseModel):
|
||||
name: str = 'Finance Dashboard'
|
||||
api_version: str = 'v1'
|
||||
database: Literal['ok'] = 'ok'
|
||||
Reference in New Issue
Block a user