first commit

This commit is contained in:
debu-2 2026-10-02 11:22:40 +05:30
commit efa5080897
127 changed files with 18610 additions and 0 deletions

133
backend/app/routes_kyc.py Normal file
View file

@ -0,0 +1,133 @@
from datetime import datetime, timezone
from pathlib import Path
from uuid import UUID
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import FileResponse
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from .auth import current_user
from .config import settings
from .database import get_db
from .kyc_constants import ALLOWED_UPLOAD_EXTENSIONS, KYC_DOCUMENT_TYPES
from .kyc_service import ensure_kyc_application, refresh_application_status, serialize_kyc, upload_root
from .kyc_validators import validate_gst, validate_pan
from .models import KycApplication, KycDocument, UserAccount
router = APIRouter(prefix="/api/v1/kyc", tags=["kyc"])
class KycDetailsRequest(BaseModel):
pan_number: str = Field(min_length=10, max_length=10)
gst_number: str = Field(min_length=15, max_length=15)
@router.get("/template")
def download_template(_: UserAccount = Depends(current_user)) -> FileResponse:
template = Path(settings.kyc_template_path)
if not template.is_file():
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="KYC template not found")
return FileResponse(
template,
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
filename="KYC Form - Pvt Ltd Client.docx",
)
@router.get("/me")
def get_my_kyc(db: Session = Depends(get_db), user: UserAccount = Depends(current_user)) -> dict:
application = ensure_kyc_application(db, user)
return serialize_kyc(application)
@router.put("/me")
def update_my_kyc(
payload: KycDetailsRequest,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
application = ensure_kyc_application(db, user)
if application.status == "approved":
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Approved KYC cannot be edited")
try:
application.pan_number = validate_pan(payload.pan_number)
application.gst_number = validate_gst(payload.gst_number)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
if application.status == "not_started":
application.status = "in_progress"
db.commit()
db.refresh(application)
return serialize_kyc(application)
@router.post("/documents/{document_type}")
async def upload_document(
document_type: str,
file: UploadFile = File(...),
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
if document_type not in KYC_DOCUMENT_TYPES:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid document type")
application = ensure_kyc_application(db, user)
if application.status == "approved":
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Approved KYC cannot be edited")
if not application.pan_number or not application.gst_number:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Enter PAN and GST before uploading documents",
)
filename = file.filename or ""
extension = Path(filename).suffix.lower()
if extension not in ALLOWED_UPLOAD_EXTENSIONS:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Allowed file types: PDF, PNG, JPG, DOC, DOCX",
)
content = await file.read()
if not content:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="File is empty")
if len(content) > settings.max_upload_bytes:
raise HTTPException(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="File exceeds 10 MB limit")
document = next(doc for doc in application.documents if doc.document_type == document_type)
root = upload_root(settings.upload_dir)
stored_name = f"{application.id}_{document_type}{extension}"
stored_path = root / stored_name
stored_path.write_bytes(content)
document.original_filename = filename
document.stored_path = str(stored_path)
document.status = "pending"
document.review_note = None
document.uploaded_at = datetime.now(timezone.utc)
document.reviewed_at = None
if application.status in {"not_started", "in_progress", "rejected"}:
application.status = "in_progress"
db.commit()
db.refresh(application)
return serialize_kyc(application)
@router.post("/submit")
def submit_kyc(db: Session = Depends(get_db), user: UserAccount = Depends(current_user)) -> dict:
application = ensure_kyc_application(db, user)
if application.status == "approved":
return serialize_kyc(application)
if not application.pan_number or not application.gst_number:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="PAN and GST are required")
missing = [doc.document_type for doc in application.documents if doc.status == "missing"]
if missing:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Upload all required documents before submitting",
)
application.status = "submitted"
application.submitted_at = datetime.now(timezone.utc)
db.commit()
db.refresh(application)
return serialize_kyc(application)