-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwps.py
More file actions
153 lines (130 loc) · 4.42 KB
/
Copy pathwps.py
File metadata and controls
153 lines (130 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""WPS drawings API router.
Provides REST endpoints for creating, retrieving, and updating KMZ drawing
files stored in S3 and served through CloudFront. All routes are prefixed
with /api/wps/v1 per SWISSGEO API standards.
"""
import uuid
from typing import Annotated
from fastapi import APIRouter, File, Form, Request, UploadFile
from fastapi.responses import Response, StreamingResponse
from app.core.drawings import DrawingsService, DrawingsServiceDep
from app.core.s3 import CACHE_CONTROL_NO_STORE
from app.schemas.drawings import DrawingsCreateResponse, DrawingsUpdateResponse
from app.schemas.errors import ErrorResponse
from app.settings import get_settings
settings = get_settings()
DRAWINGS_TAG = "Drawings"
router = APIRouter(prefix=settings.api_prefix, tags=[DRAWINGS_TAG])
Sha256Form = Annotated[
str,
Form(
pattern=r"^[0-9a-fA-F]{64}$",
description=(
"SHA-256 hex digest of the KMZ file bytes (not of the multipart body), "
"computed by the client before upload. Case-insensitive."
),
),
]
KmzFile = Annotated[
UploadFile,
File(description="The KMZ file to upload. Only KMZ files are accepted."),
]
AdminIdForm = Annotated[
uuid.UUID,
Form(
description="Admin identifier required to authorize the operation",
examples=["00000000-0000-0000-0000-000000000000"],
),
]
@router.post(
"/drawings",
status_code=201,
responses={
400: {"model": ErrorResponse},
413: {"model": ErrorResponse},
500: {"model": ErrorResponse},
},
)
async def create_drawing(
request: Request,
file: KmzFile,
drawings: DrawingsServiceDep,
sha256: Sha256Form,
) -> DrawingsCreateResponse:
"""Upload a KMZ drawing file to S3 and return its access URL.
Only KMZ files are accepted. The client must provide the SHA-256 hex
digest of the file bytes, which is verified against the received content
before storing. Returns the drawing ID, admin ID, and the access URL.
"""
return await drawings.create_drawing(file, request, sha256)
@router.get(
"/drawings/{drawing_id}",
responses={
404: {"model": ErrorResponse},
500: {"model": ErrorResponse},
},
)
async def get_drawing(
drawing_id: uuid.UUID,
drawings: DrawingsServiceDep,
) -> StreamingResponse:
"""Retrieve a KMZ drawing file by its identifier.
Streams the KMZ binary content directly from S3 with the appropriate
Content-Type and Content-Disposition headers for an attachment download.
"""
stream, _ = await drawings.get_drawing(drawing_id)
return StreamingResponse(
stream,
media_type=DrawingsService.KMZ_CONTENT_TYPE,
headers={
"Content-Disposition": f'attachment; filename="{drawing_id}.kmz"',
"Cache-Control": CACHE_CONTROL_NO_STORE,
},
)
@router.put(
"/drawings/{drawing_id}",
response_model=DrawingsUpdateResponse,
responses={
400: {"model": ErrorResponse},
403: {"model": ErrorResponse},
404: {"model": ErrorResponse},
413: {"model": ErrorResponse},
500: {"model": ErrorResponse},
},
)
async def update_drawing( # noqa: PLR0913, PLR0917
request: Request,
drawing_id: uuid.UUID,
admin_id: AdminIdForm,
file: KmzFile,
sha256: Sha256Form,
drawings: DrawingsServiceDep,
) -> DrawingsUpdateResponse:
"""Update an existing KMZ drawing by overwriting it at the same S3 key.
Only KMZ files are accepted. The admin_id must match the stored drawing
metadata, otherwise the request is rejected with 403. If the new content
is identical to the stored one, the request succeeds without re-uploading.
Returns the drawing ID, access token, access URL, and creation/update
timestamps.
"""
return await drawings.update_drawing(drawing_id, admin_id, file, request, sha256)
@router.delete(
"/drawings/{drawing_id}",
status_code=204,
responses={
403: {"model": ErrorResponse},
404: {"model": ErrorResponse},
500: {"model": ErrorResponse},
},
)
async def delete_drawing(
drawing_id: uuid.UUID,
admin_id: AdminIdForm,
drawings: DrawingsServiceDep,
) -> Response:
"""Delete a KMZ drawing.
The admin_id must match the stored drawing metadata, otherwise the request
is rejected with 403. The deletion is permanent.
"""
await drawings.delete_drawing(drawing_id, admin_id)
return Response(status_code=204)