- A30 — `require_scope()` câblé : 69 sites stricts de api_v2.py passent par la factory (Bearer + scope en 1 appel, contrôle manuel supprimé) ; sémantique alignée sur celle des handlers (pas de default "read" → 0 changement de comportement) ; 12 top-level morts supprimés (0 ref app ET tests) : unsync_block, find_referring, _b64url, strip_markdown, format_number, get_auto_property_value, get_next_unique_id, local_date_in_tz, verify_device_token, _get_dynamic_groups, _require_user_gitea, validate_upload_request - A37 — CORS sans `*` : origines = app_base_url + allow_origin_regex (localhost/dev, origines d'extension pour le Web Clipper), méthodes et entêtes minutées, allow_credentials explicite + test test_cors_no_star - A39 — htmx : décision « rien » documentée (32 attributs hx-* réels sur 6 templates, conversion = refonte du view-switching sans test E2E) - A40 — version d'assets à source unique : ENV.globals["asset_version"] lu au boot depuis le fichier VERSION ; littéraux `?v=` de base.html éliminés ; test test_asset_version_single_source - A41 — app.css : 91 règles mortes purgées (-10 274 octets, 121 618 → 111 344), scan templates/JS/CSS/Python à 0 référence suite **1031/1031** · `ruff check app tests` OK · OpenAPI 511 chemins / 7.4.0 docs (ROADMAP/CHANGELOG/WORKLOAD/VERSION) à jour
76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
"""FlowDeck — Pydantic request models for API validation."""
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
from app.middleware.security import ALLOWED_EXTENSIONS, _ext
|
|
|
|
# ── File Save ────────────────────────────────────────────────
|
|
|
|
class FileSaveRequest(BaseModel):
|
|
"""Request model for saving/updating a file via Gitea."""
|
|
path: str = Field(..., min_length=1, description="File path in the repository")
|
|
content: str = Field(..., description="File content (UTF-8 encoded)")
|
|
message: str = Field(default="Update via FlowDeck", description="Commit message")
|
|
sha: str | None = Field(default=None, description="SHA of the file being updated (required for updates)")
|
|
|
|
@model_validator(mode="after")
|
|
def validate_path_extension(self):
|
|
ext = _ext(self.path)
|
|
if ext and ext not in ALLOWED_EXTENSIONS:
|
|
raise ValueError(f"File extension '{ext}' is not allowed")
|
|
return self
|
|
|
|
|
|
# ── Upload ───────────────────────────────────────────────────
|
|
|
|
class UploadValidationResult(BaseModel):
|
|
"""Result of validating an upload."""
|
|
filename: str
|
|
size: int
|
|
extension: str
|
|
valid: bool
|
|
error: str | None = None
|
|
|
|
|
|
|
|
|
|
class IssueCreateRequest(BaseModel):
|
|
"""Request model for creating a Gitea issue."""
|
|
title: str = Field(..., min_length=1, max_length=500)
|
|
body: str = Field(default="")
|
|
labels: str = Field(default="", description="Comma-separated label IDs")
|
|
milestone: str = Field(default="", description="Milestone ID")
|
|
assignee: str = Field(default="")
|
|
|
|
|
|
class IssueUpdateRequest(BaseModel):
|
|
"""Request model for updating a Gitea issue (partial update)."""
|
|
title: str | None = Field(default=None, max_length=500)
|
|
body: str | None = Field(default=None)
|
|
state: str | None = Field(default=None, pattern=r"^(open|closed)$")
|
|
labels: str | None = Field(default=None, description="Comma-separated label IDs")
|
|
milestone: str | None = Field(default=None)
|
|
assignee: str | None = Field(default=None)
|
|
|
|
|
|
# ── Card Move ────────────────────────────────────────────────
|
|
|
|
class CardMoveRequest(BaseModel):
|
|
"""Request model for moving a card between columns."""
|
|
owner: str = Field(..., min_length=1)
|
|
repo: str = Field(..., min_length=1)
|
|
issue_id: int = Field(..., gt=0)
|
|
column: str = Field(..., min_length=1)
|
|
|
|
|
|
# ── Column Mapping ───────────────────────────────────────────
|
|
|
|
class ColMappingRequest(BaseModel):
|
|
"""Request model for column-to-label mapping."""
|
|
owner: str = Field(..., min_length=1)
|
|
repo: str = Field(..., min_length=1)
|
|
column: str = Field(..., min_length=1)
|
|
gitea_label: str = Field(..., min_length=1)
|
|
close_issue: bool = Field(default=False)
|