memleketmeselesi/mm_api/routers/kyc.py
Mukan Erkin 2498e75594 init: memleketmeselesi platform — API + migrations
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.
2026-04-27 23:06:59 +03:00

144 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import uuid
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
from fastapi.responses import Response
from psycopg import AsyncConnection
from mm_api.db import get_conn
from mm_api.dependencies import current_user
from pydantic import BaseModel
import mm_api.services.kyc as svc
import mm_api.services.permission as perm_svc
router = APIRouter(prefix="/kyc", tags=["kyc"])
ALLOWED_MIME = {"image/jpeg", "image/png"}
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB
class RejectRequest(BaseModel):
note: str
async def _require_kyc_perm(action: str, user: dict, conn: AsyncConnection):
if not await perm_svc.can(conn, user["id"], action, "kyc", is_admin=True):
raise HTTPException(403, "Yetersiz yetki")
def _validate_file(file: UploadFile):
if file.content_type not in ALLOWED_MIME:
raise HTTPException(400, f"Geçersiz dosya türü: {file.content_type}")
@router.post("/submit", status_code=201)
async def submit(
tc_kimlik: str = Form(..., min_length=11, max_length=11, pattern=r"^\d{11}$"),
id_front: UploadFile = File(...),
id_back: UploadFile = File(...),
selfie: UploadFile = File(...),
user: dict = Depends(current_user),
conn: AsyncConnection = Depends(get_conn),
):
for f in (id_front, id_back, selfie):
_validate_file(f)
req_uuid = str(uuid.uuid4())
front_data = await id_front.read()
back_data = await id_back.read()
selfie_data = await selfie.read()
for data, name in ((front_data, "id_front"), (back_data, "id_back"), (selfie_data, "selfie")):
if len(data) > MAX_FILE_SIZE:
raise HTTPException(400, f"{name} dosyası 5MB'dan büyük olamaz")
front_path = svc.save_file_encrypted(req_uuid, "id_front", front_data)
back_path = svc.save_file_encrypted(req_uuid, "id_back", back_data)
selfie_path = svc.save_file_encrypted(req_uuid, "selfie", selfie_data)
try:
return await svc.submit(conn, user["id"], tc_kimlik, front_path, back_path, selfie_path)
except ValueError as e:
svc.delete_request_files(req_uuid)
raise HTTPException(400, str(e))
@router.get("/pending")
async def list_pending(
user: dict = Depends(current_user),
conn: AsyncConnection = Depends(get_conn),
):
await _require_kyc_perm("view", user, conn)
return await svc.list_pending(conn)
@router.post("/{request_id}/view-token")
async def get_view_token(
request_id: int,
user: dict = Depends(current_user),
conn: AsyncConnection = Depends(get_conn),
):
await _require_kyc_perm("view", user, conn)
req = await svc.get_request(conn, request_id)
if not req:
raise HTTPException(404, "Başvuru bulunamadı")
token = await svc.create_view_token(conn, request_id, user["id"])
return {"view_token": token, "expires_in": 600}
@router.get("/view/{token}/{file_type}")
async def view_file(
token: str,
file_type: str,
conn: AsyncConnection = Depends(get_conn),
):
if file_type not in ("id_front", "id_back", "selfie"):
raise HTTPException(400, "Geçersiz dosya türü")
req = await svc.consume_view_token(conn, token)
if not req:
raise HTTPException(404, "Geçersiz veya süresi dolmuş token")
path_key = f"{file_type}_path"
data = svc.read_file_encrypted(req[path_key])
return Response(content=data, media_type="image/jpeg")
@router.post("/{request_id}/approve")
async def approve(
request_id: int,
user: dict = Depends(current_user),
conn: AsyncConnection = Depends(get_conn),
):
await _require_kyc_perm("approve", user, conn)
try:
return await svc.approve(conn, request_id, user["id"])
except ValueError as e:
raise HTTPException(400, str(e))
@router.post("/{request_id}/reject")
async def reject(
request_id: int,
data: RejectRequest,
user: dict = Depends(current_user),
conn: AsyncConnection = Depends(get_conn),
):
await _require_kyc_perm("reject", user, conn)
try:
return await svc.reject(conn, request_id, user["id"], data.note)
except ValueError as e:
raise HTTPException(400, str(e))
@router.get("/users/{user_id}/tc")
async def get_tc(
user_id: int,
user: dict = Depends(current_user),
conn: AsyncConnection = Depends(get_conn),
):
"""Adli soruşturma endpoint'i — TC kimlik nosunu döner."""
if not await perm_svc.can(conn, user["id"], "view", "kyc", is_admin=True):
raise HTTPException(403, "Yetersiz yetki")
tc = await svc.get_tc_kimlik(conn, user_id)
if tc is None:
raise HTTPException(404, "Kimlik bilgisi bulunamadı")
return {"user_id": user_id, "tc_kimlik": tc}