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.
193 lines
5.8 KiB
Python
193 lines
5.8 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, verified_user
|
||
import mm_api.services.issue as svc
|
||
import mm_api.services.permission as perm_svc
|
||
|
||
router = APIRouter(prefix="/issues", tags=["issues"])
|
||
|
||
|
||
class IssueCreate(BaseModel):
|
||
title: str = Field(..., min_length=5, max_length=300)
|
||
body: str = Field(..., min_length=20)
|
||
category_id: int
|
||
location_id: int
|
||
|
||
|
||
class IssueUpdate(BaseModel):
|
||
title: str | None = Field(None, min_length=5, max_length=300)
|
||
body: str | None = Field(None, min_length=20)
|
||
|
||
|
||
class StatusUpdate(BaseModel):
|
||
status: Literal["open", "in_progress", "resolved", "rejected", "duplicate"]
|
||
|
||
|
||
class VoteRequest(BaseModel):
|
||
vote: Literal["resolved", "ongoing"]
|
||
|
||
|
||
class CommentCreate(BaseModel):
|
||
body: str = Field(..., min_length=1, max_length=5000)
|
||
parent_id: int | None = None
|
||
|
||
|
||
@router.get("/categories")
|
||
async def list_categories(
|
||
parent_id: int | None = Query(None),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
return await svc.list_categories(conn, parent_id)
|
||
|
||
|
||
@router.get("/categories/{category_id}")
|
||
async def get_category(
|
||
category_id: int,
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
cat = await svc.get_category(conn, category_id)
|
||
if not cat:
|
||
raise HTTPException(404, "Kategori bulunamadı")
|
||
return cat
|
||
|
||
|
||
@router.get("")
|
||
async def list_issues(
|
||
location_id: int | None = Query(None),
|
||
category_id: int | None = Query(None),
|
||
status: 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_issues(conn, location_id, category_id, status, limit=limit, offset=offset)
|
||
|
||
|
||
@router.post("", status_code=201)
|
||
async def create_issue(
|
||
data: IssueCreate,
|
||
user: dict = Depends(verified_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
if not await perm_svc.can(conn, user["id"], "create", "issue"):
|
||
raise HTTPException(403, "Sorun bildirme yetkiniz yok")
|
||
try:
|
||
return await svc.create_issue(conn, user["id"], data.title, data.body, data.category_id, data.location_id)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
@router.get("/{issue_id}")
|
||
async def get_issue(
|
||
issue_id: int,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
issue = await svc.get_issue(conn, issue_id)
|
||
if not issue:
|
||
raise HTTPException(404, "Sorun bulunamadı")
|
||
return issue
|
||
|
||
|
||
@router.patch("/{issue_id}")
|
||
async def update_issue(
|
||
issue_id: int,
|
||
data: IssueUpdate,
|
||
user: dict = Depends(verified_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
is_admin = await perm_svc.can(conn, user["id"], "update", "issue", is_admin=True)
|
||
if not is_admin and not await perm_svc.can(conn, user["id"], "update", "issue"):
|
||
raise HTTPException(403, "Yetersiz yetki")
|
||
try:
|
||
return await svc.update_issue(conn, issue_id, user["id"], is_admin, data.title, data.body)
|
||
except PermissionError as e:
|
||
raise HTTPException(403, str(e))
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
@router.put("/{issue_id}/status")
|
||
async def update_status(
|
||
issue_id: int,
|
||
data: StatusUpdate,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
if not await perm_svc.can(conn, user["id"], "status", "issue", is_admin=True):
|
||
raise HTTPException(403, "Yetersiz yetki")
|
||
try:
|
||
return await svc.update_status(conn, issue_id, data.status)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
@router.delete("/{issue_id}", status_code=204)
|
||
async def delete_issue(
|
||
issue_id: int,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
if not await perm_svc.can(conn, user["id"], "delete", "issue", is_admin=True):
|
||
raise HTTPException(403, "Yetersiz yetki")
|
||
try:
|
||
await svc.delete_issue(conn, issue_id)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
@router.post("/{issue_id}/vote")
|
||
async def vote(
|
||
issue_id: int,
|
||
data: VoteRequest,
|
||
user: dict = Depends(verified_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
if not await perm_svc.can(conn, user["id"], "create", "vote"):
|
||
raise HTTPException(403, "Oy kullanma yetkiniz yok")
|
||
try:
|
||
return await svc.cast_vote(conn, issue_id, user["id"], data.vote)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
@router.get("/{issue_id}/comments")
|
||
async def list_comments(
|
||
issue_id: int,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
return await svc.list_comments(conn, issue_id)
|
||
|
||
|
||
@router.post("/{issue_id}/comments", status_code=201)
|
||
async def add_comment(
|
||
issue_id: int,
|
||
data: CommentCreate,
|
||
user: dict = Depends(verified_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
if not await perm_svc.can(conn, user["id"], "create", "comment"):
|
||
raise HTTPException(403, "Yorum yapma yetkiniz yok")
|
||
try:
|
||
return await svc.add_comment(conn, issue_id, user["id"], data.body, data.parent_id)
|
||
except ValueError as e:
|
||
raise HTTPException(400, str(e))
|
||
|
||
|
||
@router.delete("/comments/{comment_id}", status_code=204)
|
||
async def delete_comment(
|
||
comment_id: int,
|
||
user: dict = Depends(current_user),
|
||
conn: AsyncConnection = Depends(get_conn),
|
||
):
|
||
is_admin = await perm_svc.can(conn, user["id"], "delete", "comment", is_admin=True)
|
||
try:
|
||
await svc.delete_comment(conn, comment_id, user["id"], is_admin)
|
||
except PermissionError as e:
|
||
raise HTTPException(403, str(e))
|
||
except ValueError as e:
|
||
raise HTTPException(404, str(e))
|