Initial commit: svg backend

This commit is contained in:
adminko
2026-03-19 13:39:32 +03:00
commit 85fb2f4bb9
78 changed files with 6161 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
from typing import List
from pydantic import BaseModel
class AuditEventItem(BaseModel):
audit_event_id: str
scheme_id: str
event_type: str
object_type: str
object_ref: str | None
details_json: str | None
created_at: str
class SchemeAuditResponse(BaseModel):
items: List[AuditEventItem]
total: int

View File

@@ -0,0 +1,28 @@
from pydantic import BaseModel
class SvgLimitsManifest(BaseModel):
max_file_size_bytes: int
max_elements: int
class SanitizationManifest(BaseModel):
allow_internal_use_references_only: bool
forbid_foreign_object_v1: bool
forbid_style_v1: bool
forbid_image_v1: bool
allowed_data_attributes: list[str]
class ExtractionContractManifest(BaseModel):
seat_fields: list[str]
priority: list[str]
class ServiceManifestResponse(BaseModel):
service: str
api_prefix: str
auth_header_name: str
svg_limits: SvgLimitsManifest
sanitization: SanitizationManifest
extraction_contract: ExtractionContractManifest

View File

@@ -0,0 +1,179 @@
from decimal import Decimal, InvalidOperation
from typing import List
from pydantic import BaseModel, field_validator
class PricingCategoryCreateRequest(BaseModel):
name: str
code: str | None = None
class PricingCategoryUpdateRequest(BaseModel):
name: str
code: str | None = None
class PricingCategoryCreateResponse(BaseModel):
pricing_category_id: str
scheme_id: str
name: str
code: str | None
class PricingCategoryUpdateResponse(BaseModel):
pricing_category_id: str
scheme_id: str
name: str
code: str | None
class DeleteResponse(BaseModel):
status: str
class PriceRuleCreateRequest(BaseModel):
pricing_category_id: str | None = None
target_type: str
target_ref: str
amount: Decimal
currency: str = "RUB"
@field_validator("target_type")
@classmethod
def validate_target_type(cls, value: str) -> str:
allowed = {"sector", "group", "seat"}
if value not in allowed:
raise ValueError("Поле target_type должно быть одним из: sector, group, seat")
return value
@field_validator("currency")
@classmethod
def validate_currency(cls, value: str) -> str:
if value != "RUB":
raise ValueError("В v1 поддерживается только валюта RUB")
return value
@field_validator("amount", mode="before")
@classmethod
def parse_amount(cls, value):
if value is None:
raise ValueError("Поле amount обязательно")
text = str(value).strip()
if text == "":
raise ValueError("Поле amount обязательно")
try:
return Decimal(text)
except (InvalidOperation, ValueError):
raise ValueError("Некорректная сумма. Используйте формат 2500.00")
@field_validator("amount")
@classmethod
def validate_amount(cls, value: Decimal) -> Decimal:
if value < Decimal("0.00"):
raise ValueError("Сумма не может быть отрицательной")
if value.quantize(Decimal("0.01")) != value:
raise ValueError("Сумма должна быть с точностью до 2 знаков после запятой")
return value
class PriceRuleUpdateRequest(BaseModel):
pricing_category_id: str | None = None
target_type: str
target_ref: str
amount: Decimal
currency: str = "RUB"
@field_validator("target_type")
@classmethod
def validate_target_type(cls, value: str) -> str:
allowed = {"sector", "group", "seat"}
if value not in allowed:
raise ValueError("Поле target_type должно быть одним из: sector, group, seat")
return value
@field_validator("currency")
@classmethod
def validate_currency(cls, value: str) -> str:
if value != "RUB":
raise ValueError("В v1 поддерживается только валюта RUB")
return value
@field_validator("amount", mode="before")
@classmethod
def parse_amount(cls, value):
if value is None:
raise ValueError("Поле amount обязательно")
text = str(value).strip()
if text == "":
raise ValueError("Поле amount обязательно")
try:
return Decimal(text)
except (InvalidOperation, ValueError):
raise ValueError("Некорректная сумма. Используйте формат 2500.00")
@field_validator("amount")
@classmethod
def validate_amount(cls, value: Decimal) -> Decimal:
if value < Decimal("0.00"):
raise ValueError("Сумма не может быть отрицательной")
if value.quantize(Decimal("0.01")) != value:
raise ValueError("Сумма должна быть с точностью до 2 знаков после запятой")
return value
class PriceRuleCreateResponse(BaseModel):
price_rule_id: str
scheme_id: str
pricing_category_id: str | None
target_type: str
target_ref: str
amount: Decimal
currency: str
class PriceRuleUpdateResponse(BaseModel):
price_rule_id: str
scheme_id: str
pricing_category_id: str | None
target_type: str
target_ref: str
amount: Decimal
currency: str
class PricingCategoryItem(BaseModel):
pricing_category_id: str
scheme_id: str
name: str
code: str | None
created_at: str
class PriceRuleItem(BaseModel):
price_rule_id: str
scheme_id: str
pricing_category_id: str | None
target_type: str
target_ref: str
amount: Decimal
currency: str
created_at: str
class SchemePricingResponse(BaseModel):
categories: List[PricingCategoryItem]
rules: List[PriceRuleItem]
class EffectiveSeatPriceResponse(BaseModel):
scheme_id: str
scheme_version_id: str
seat_id: str
sector_id: str | None
group_id: str | None
matched_rule_level: str
matched_target_ref: str
pricing_category_id: str | None
amount: Decimal
currency: str

View File

@@ -0,0 +1,19 @@
from typing import List
from pydantic import BaseModel
class SchemeGroupItem(BaseModel):
group_record_id: str
scheme_id: str
scheme_version_id: str
element_id: str | None
group_id: str | None
name: str
classes_raw: str | None
created_at: str
class SchemeGroupListResponse(BaseModel):
items: List[SchemeGroupItem]
total: int

View File

@@ -0,0 +1,67 @@
from typing import List
from pydantic import BaseModel
class SchemeListItem(BaseModel):
scheme_id: str
source_upload_id: str
name: str
status: str
current_version_number: int
published_at: str | None
normalized_elements_count: int
normalized_seats_count: int
normalized_groups_count: int
normalized_sectors_count: int
created_at: str
class SchemeListResponse(BaseModel):
items: List[SchemeListItem]
total: int
class SchemeDetailResponse(BaseModel):
scheme_id: str
source_upload_id: str
name: str
status: str
current_version_number: int
published_at: str | None
normalized_elements_count: int
normalized_seats_count: int
normalized_groups_count: int
normalized_sectors_count: int
created_at: str
class SchemeCurrentResponse(BaseModel):
scheme_id: str
scheme_version_id: str
version_number: int
status: str
normalized_storage_path: str
normalized_elements_count: int
normalized_seats_count: int
normalized_groups_count: int
normalized_sectors_count: int
created_at: str
class SchemePublishResponse(BaseModel):
scheme_id: str
status: str
current_version_number: int
published_at: str | None
class SchemeRollbackRequest(BaseModel):
target_version_number: int
class SchemeRollbackResponse(BaseModel):
scheme_id: str
status: str
current_version_number: int
published_at: str | None

View File

@@ -0,0 +1,29 @@
from typing import List
from pydantic import BaseModel
class SchemeSeatItem(BaseModel):
seat_record_id: str
scheme_id: str
scheme_version_id: str
element_id: str | None
seat_id: str | None
sector_id: str | None
group_id: str | None
row_label: str | None
seat_number: str | None
tag: str | None
classes_raw: str | None
x: float | None
y: float | None
cx: float | None
cy: float | None
width: float | None
height: float | None
created_at: str
class SchemeSeatListResponse(BaseModel):
items: List[SchemeSeatItem]
total: int

View File

@@ -0,0 +1,19 @@
from typing import List
from pydantic import BaseModel
class SchemeSectorItem(BaseModel):
sector_record_id: str
scheme_id: str
scheme_version_id: str
element_id: str | None
sector_id: str | None
name: str
classes_raw: str | None
created_at: str
class SchemeSectorListResponse(BaseModel):
items: List[SchemeSectorItem]
total: int

View File

@@ -0,0 +1,29 @@
from typing import List
from pydantic import BaseModel
class SchemeVersionListItem(BaseModel):
scheme_version_id: str
scheme_id: str
version_number: int
status: str
normalized_storage_path: str
normalized_elements_count: int
normalized_seats_count: int
normalized_groups_count: int
normalized_sectors_count: int
created_at: str
class SchemeVersionListResponse(BaseModel):
items: List[SchemeVersionListItem]
total: int
class SchemeVersionCreateResponse(BaseModel):
scheme_id: str
scheme_version_id: str
version_number: int
status: str
normalized_storage_path: str

View File

@@ -0,0 +1,21 @@
from decimal import Decimal
from pydantic import BaseModel
class TestSeatPreviewResponse(BaseModel):
scheme_id: str
scheme_version_id: str
seat_id: str
element_id: str | None
sector_id: str | None
group_id: str | None
row_label: str | None
seat_number: str | None
selectable: bool
has_price: bool
matched_rule_level: str | None
matched_target_ref: str | None
pricing_category_id: str | None
amount: Decimal | None
currency: str | None

View File

@@ -0,0 +1,21 @@
from pydantic import BaseModel
class UploadResponse(BaseModel):
upload_id: str
filename: str
content_type: str
size_bytes: int
element_count: int
removed_elements_count: int
removed_attributes_count: int
normalized_elements_count: int
normalized_seats_count: int
normalized_groups_count: int
normalized_sectors_count: int
svg_max_file_size_bytes: int
svg_max_elements: int
original_storage_path: str
sanitized_storage_path: str
normalized_storage_path: str
accepted: bool

View File

@@ -0,0 +1,46 @@
from typing import List
from pydantic import BaseModel
class UploadListItem(BaseModel):
upload_id: str
original_filename: str
content_type: str
size_bytes: int
element_count: int
removed_elements_count: int
removed_attributes_count: int
normalized_elements_count: int
normalized_seats_count: int
normalized_groups_count: int
normalized_sectors_count: int
original_storage_path: str
sanitized_storage_path: str
normalized_storage_path: str
processing_status: str
created_at: str
class UploadListResponse(BaseModel):
items: List[UploadListItem]
total: int
class UploadDetailResponse(BaseModel):
upload_id: str
original_filename: str
content_type: str
size_bytes: int
element_count: int
removed_elements_count: int
removed_attributes_count: int
normalized_elements_count: int
normalized_seats_count: int
normalized_groups_count: int
normalized_sectors_count: int
original_storage_path: str
sanitized_storage_path: str
normalized_storage_path: str
processing_status: str
created_at: str