FastAPI + PostgreSQL 16. KYC, issue sistemi, permission/group yönetimi, session yönetimi, API client auth (kışla kapısı), officials/persons CRUD. Migration 0001–0013 dahil.
166 lines
5 KiB
Python
166 lines
5 KiB
Python
from typing import Literal
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from psycopg import AsyncConnection
|
||
from pydantic import BaseModel, Field
|
||
from mm_api.db import get_conn
|
||
from mm_api.dependencies import current_user
|
||
import mm_api.services.official as svc
|
||
import mm_api.services.permission as perm_svc
|
||
|
||
router = APIRouter(tags=["officials"])
|
||
|
||
|
||
async def _require_profile_perm(action: str, user: dict, conn: AsyncConnection):
|
||
if not await perm_svc.can(conn, user["id"], action, "profile", is_admin=True):
|
||
raise HTTPException(403, "Yetersiz yetki")
|
||
|
||
|
||
class PersonCreate(BaseModel):
|
||
first_name: str = Field(..., min_length=1, max_length=100)
|
||
last_name: str = Field(..., min_length=1, max_length=100)
|
||
birth_year: int | None = Field(None, ge=1900, le=2010)
|
||
|
||
|
||
class PersonUpdate(BaseModel):
|
||
first_name: str | None = Field(None, min_length=1, max_length=100)
|
||
last_name: str | None = Field(None, min_length=1, max_length=100)
|
||
birth_year: int | None = Field(None, ge=1900, le=2010)
|
||
|
||
|
||
class ElectionCreate(BaseModel):
|
||
name: str = Field(..., min_length=2, max_length=200)
|
||
held_at: str # YYYY-MM-DD
|
||
type: Literal["genel", "yerel", "cumhurbaskanligi", "referandum"]
|
||
|
||
|
||
class OfficialCreate(BaseModel):
|
||
person_id: int
|
||
unit_id: int
|
||
title: str = Field(..., min_length=2, max_length=200)
|
||
started_at: str # YYYY-MM-DD
|
||
election_id: int | None = None
|
||
|
||
|
||
class OfficialClose(BaseModel):
|
||
ended_at: str # YYYY-MM-DD
|
||
|
||
|
||
# --- Persons ---
|
||
|
||
@router.get("/persons")
|
||
async def list_persons(
|
||
q: str | None = Query(None),
|
||
limit: int = Query(20, ge=1, le=100),
|
||
offset: int = Query(0, ge=0),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
return await svc.list_persons(conn, q, limit, offset)
|
||
|
||
|
||
@router.get("/persons/{person_id}")
|
||
async def get_person(
|
||
person_id: int,
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
person = await svc.get_person(conn, person_id)
|
||
if not person:
|
||
raise HTTPException(404, "Kişi bulunamadı")
|
||
officials = await svc.get_person_officials(conn, person_id)
|
||
return {**person, "officials": officials}
|
||
|
||
|
||
@router.post("/persons", status_code=201)
|
||
async def create_person(
|
||
data: PersonCreate,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
await _require_profile_perm("create", user, conn)
|
||
return await svc.create_person(conn, data.first_name, data.last_name, data.birth_year)
|
||
|
||
|
||
@router.patch("/persons/{person_id}")
|
||
async def update_person(
|
||
person_id: int,
|
||
data: PersonUpdate,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
await _require_profile_perm("update", user, conn)
|
||
person = await svc.get_person(conn, person_id)
|
||
if not person:
|
||
raise HTTPException(404, "Kişi bulunamadı")
|
||
try:
|
||
return await svc.update_person(conn, person_id, data.first_name, data.last_name, data.birth_year)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
# --- Elections ---
|
||
|
||
@router.get("/elections")
|
||
async def list_elections(conn: AsyncConnection = Depends(get_conn)):
|
||
return await svc.list_elections(conn)
|
||
|
||
|
||
@router.post("/elections", status_code=201)
|
||
async def create_election(
|
||
data: ElectionCreate,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
await _require_profile_perm("create", user, conn)
|
||
return await svc.create_election(conn, data.name, data.held_at, data.type)
|
||
|
||
|
||
# --- Officials ---
|
||
|
||
@router.get("/officials")
|
||
async def list_officials(
|
||
person_id: int | None = Query(None),
|
||
unit_id: int | None = Query(None),
|
||
active_only: bool = Query(False),
|
||
limit: int = Query(20, ge=1, le=100),
|
||
offset: int = Query(0, ge=0),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
return await svc.list_officials(conn, person_id, unit_id, active_only, limit, offset)
|
||
|
||
|
||
@router.get("/officials/{official_id}")
|
||
async def get_official(
|
||
official_id: int,
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
official = await svc.get_official(conn, official_id)
|
||
if not official:
|
||
raise HTTPException(404, "Yetkili kaydı bulunamadı")
|
||
stats = await svc.get_official_stats(conn, official_id)
|
||
return {**official, "stats": stats}
|
||
|
||
|
||
@router.post("/officials", status_code=201)
|
||
async def create_official(
|
||
data: OfficialCreate,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
await _require_profile_perm("create", user, conn)
|
||
try:
|
||
return await svc.create_official(conn, data.person_id, data.unit_id, data.title, data.started_at, data.election_id)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
@router.post("/officials/{official_id}/close")
|
||
async def close_official(
|
||
official_id: int,
|
||
data: OfficialClose,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
await _require_profile_perm("update", user, conn)
|
||
try:
|
||
return await svc.close_official(conn, official_id, data.ended_at)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|