This commit is contained in:
2026-04-09 19:28:41 +03:00
commit 9fa723bb4c
43 changed files with 2804 additions and 0 deletions

4
app/api/v1/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
# app/api/v1/__init__.py
from app.api.v1 import forms, submissions, analytics, export
__all__ = ["forms", "submissions", "analytics", "export"]

46
app/api/v1/analytics.py Normal file
View File

@@ -0,0 +1,46 @@
# app/api/v1/analytics.py
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import Optional
from app.api import deps
from app.services.analytics_service import AnalyticsService
from app.schemas.response import FormAnalyticsResponse
router = APIRouter(prefix="/analytics", tags=["analytics"])
@router.get("/forms/{form_id}/report", response_model=FormAnalyticsResponse)
async def get_form_analytics(
form_id: int,
db: Session = Depends(deps.get_db),
days: int = Query(30, ge=1, le=365)
):
"""Получить аналитику по форме"""
service = AnalyticsService(db)
try:
report = service.get_form_report(form_id, days)
return report
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.get("/forms/{form_id}/fields/{field_name}/stats")
async def get_field_statistics(
form_id: int,
field_name: str,
db: Session = Depends(deps.get_db)
):
"""Получить статистику по полю формы"""
service = AnalyticsService(db)
stats = service.get_field_statistics(form_id, field_name)
if stats is None:
raise HTTPException(status_code=404, detail="Form or field not found")
return stats
@router.get("/dashboard/overview")
async def get_dashboard_overview(
db: Session = Depends(deps.get_db),
days: int = Query(30, ge=1, le=365)
):
"""Получить общую статистику по всем формам"""
service = AnalyticsService(db)
return service.get_global_overview(days)

30
app/api/v1/export.py Normal file
View File

@@ -0,0 +1,30 @@
# app/api/v1/export.py
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from typing import Optional
from app.api import deps
from app.services.export_service import ExportService
router = APIRouter(prefix="/export", tags=["export"])
@router.get("/forms/{form_id}/csv")
async def export_form_csv(
form_id: int,
format: str = Query("csv", regex="^(csv|xlsx)$"),
db: Session = Depends(deps.get_db)
):
"""Экспорт данных формы в CSV или Excel"""
service = ExportService(db)
try:
file_content, filename, media_type = service.export_form_data(form_id, format)
return StreamingResponse(
iter([file_content]),
media_type=media_type,
headers={"Content-Disposition": f"attachment; filename={filename}"}
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Export failed: {str(e)}")

91
app/api/v1/forms.py Normal file
View File

@@ -0,0 +1,91 @@
# app/api/v1/forms.py
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from typing import Optional
from app.api import deps
from app.schemas.form import FormCreate, FormUpdate, FormResponse, FormWithFieldsResponse
from app.schemas.common import MessageResponse, PaginatedResponse
from app.services.form_service import FormService
router = APIRouter(prefix="/forms", tags=["forms"])
@router.post("/", response_model=FormResponse, status_code=status.HTTP_201_CREATED)
async def create_form(
form_data: FormCreate,
db: Session = Depends(deps.get_db),
current_user: Optional[dict] = Depends(deps.get_current_user)
):
"""Создать новую форму"""
service = FormService(db)
try:
form = service.create_form(form_data, user_id=current_user.get("id") if current_user else None)
return form
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/", response_model=PaginatedResponse[FormResponse])
async def list_forms(
page: int = Query(1, ge=1),
per_page: int = Query(20, ge=1, le=100),
active_only: bool = False,
db: Session = Depends(deps.get_db)
):
"""Список форм с пагинацией"""
service = FormService(db)
result = service.get_forms_paginated(
page=page,
per_page=per_page,
active_only=active_only
)
return result
@router.get("/{form_id}")
async def get_form(
form_id: int,
db: Session = Depends(deps.get_db)
):
"""Получить форму по ID с полями"""
service = FormService(db)
form = service.get_form_with_fields(form_id)
if not form:
raise HTTPException(status_code=404, detail="Form not found")
return form
@router.put("/{form_id}", response_model=FormResponse)
async def update_form(
form_id: int,
form_data: FormUpdate,
db: Session = Depends(deps.get_db)
):
"""Обновить форму"""
service = FormService(db)
form = service.update_form(form_id, form_data)
if not form:
raise HTTPException(status_code=404, detail="Form not found")
return form
@router.delete("/{form_id}", response_model=MessageResponse)
async def delete_form(
form_id: int,
db: Session = Depends(deps.get_db)
):
"""Удалить форму"""
service = FormService(db)
success = service.delete_form(form_id)
if not success:
raise HTTPException(status_code=404, detail="Form not found")
return MessageResponse(message="Form deleted successfully")
@router.post("/{form_id}/publish", response_model=FormResponse)
async def publish_form(
form_id: int,
publish: bool = True,
db: Session = Depends(deps.get_db)
):
"""Опубликовать или скрыть форму"""
service = FormService(db)
form = service.publish_form(form_id, publish)
if not form:
raise HTTPException(status_code=404, detail="Form not found")
return form

79
app/api/v1/submissions.py Normal file
View File

@@ -0,0 +1,79 @@
# app/api/v1/submissions.py
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from typing import Optional, Dict, Any
from app.api import deps
from app.schemas.submission import SubmissionCreate, SubmissionResponse, SubmissionFilterParams
from app.schemas.common import PaginatedResponse
from app.services.submission_service import SubmissionService
router = APIRouter(prefix="/submissions", tags=["submissions"])
@router.post("/", status_code=status.HTTP_201_CREATED)
async def submit_form(
submission_data: SubmissionCreate,
db: Session = Depends(deps.get_db)
):
"""Отправить данные формы"""
service = SubmissionService(db)
try:
submission = service.create_submission(submission_data)
return submission # Теперь возвращает словарь, а не объект SQLAlchemy
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/{submission_id}", response_model=SubmissionResponse)
async def get_submission(
submission_id: int,
db: Session = Depends(deps.get_db)
):
"""Получить submission по ID"""
service = SubmissionService(db)
submission = service.get_submission_by_id(submission_id)
if not submission:
raise HTTPException(status_code=404, detail="Submission not found")
return submission
@router.get("/forms/{form_id}/submissions/", response_model=PaginatedResponse[SubmissionResponse])
async def get_form_submissions(
form_id: int,
page: int = Query(1, ge=1),
per_page: int = Query(20, ge=1, le=100),
status: Optional[str] = None,
db: Session = Depends(deps.get_db)
):
"""Получить все submission формы"""
service = SubmissionService(db)
result = service.get_submissions_by_form(
form_id=form_id,
page=page,
per_page=per_page,
status=status
)
return result
@router.put("/{submission_id}", response_model=SubmissionResponse)
async def update_submission(
submission_id: int,
data: Dict[str, Any],
db: Session = Depends(deps.get_db)
):
"""Обновить submission"""
service = SubmissionService(db)
submission = service.update_submission(submission_id, data)
if not submission:
raise HTTPException(status_code=404, detail="Submission not found")
return submission
@router.delete("/{submission_id}", response_model=Dict[str, str])
async def delete_submission(
submission_id: int,
db: Session = Depends(deps.get_db)
):
"""Удалить submission"""
service = SubmissionService(db)
success = service.delete_submission(submission_id)
if not success:
raise HTTPException(status_code=404, detail="Submission not found")
return {"message": "Submission deleted successfully"}