1616from app .auth import current_user
1717from app .database import get_db
1818from app .keycloak_admin import delete_user as kc_delete_user
19- from app .models import Image , Preset , PresetReport , User
19+ from app .models import Feedback , Image , ImageEdit , Preset , PresetReport , User
2020from app .rate_limit import limiter
2121from app .schemas import (
2222 CAMEL_BASE_CONFIG ,
@@ -48,8 +48,15 @@ async def update_profile(
4848 user : User = Depends (current_user ),
4949 db : AsyncSession = Depends (get_db ),
5050) -> ProfileOut :
51- user .handle = payload .handle
52- user .bio = payload .bio
51+ # PATCH ist partiell: nur tatsaechlich gesendete Felder anfassen, sonst
52+ # loescht ein PATCH, der z.B. nur bio schickt, stillschweigend den handle
53+ # (= oeffentlicher Marketplace-Creator-Name). exclude_unset trennt
54+ # "nicht gesendet" sauber von "explizit auf null gesetzt".
55+ data = payload .model_dump (exclude_unset = True )
56+ if "handle" in data :
57+ user .handle = data ["handle" ]
58+ if "bio" in data :
59+ user .bio = data ["bio" ]
5360 try :
5461 await db .commit ()
5562 except IntegrityError :
@@ -137,6 +144,10 @@ class ImageExport(BaseModel):
137144 confirmed_at : datetime | None
138145 download_url : str
139146 download_url_expires_in : int
147+ # Persistierter Editor-Bearbeitungsstand (C1) — User-erzeugter Inhalt,
148+ # gehoert in einen vollstaendigen Art.-15/20-Export. None, wenn das Bild
149+ # nie im Editor bearbeitet/gespeichert wurde.
150+ edit_state : dict | None = None
140151
141152
142153class PresetExport (BaseModel ):
@@ -147,6 +158,9 @@ class PresetExport(BaseModel):
147158 name : str
148159 adjustments : dict
149160 masks : list
161+ # Crop/Straighten/Lens-Geometrie ist User-erzeugter Inhalt (Migration
162+ # 009) und gehoert in den Export.
163+ geometry : dict | None
150164 visibility : str
151165 genre : str | None
152166 description : str | None
@@ -169,6 +183,19 @@ class ReportExport(BaseModel):
169183 created_at : datetime
170184
171185
186+ class FeedbackExport (BaseModel ):
187+ """Vom User abgegebenes Feedback. Der Message-Text ist freier User-Inhalt
188+ und auskunfts-/uebertragbarkeitspflichtig (Art. 15 + 20) — und bleibt bei
189+ Loeschung nur anonymisiert erhalten, ist also gerade vorher exportwichtig."""
190+ model_config = CAMEL_OUT_CONFIG
191+ id : UUID
192+ kind : str
193+ message : str
194+ page : str | None
195+ status : str
196+ created_at : datetime
197+
198+
172199class MeExport (BaseModel ):
173200 model_config = CAMEL_BASE_CONFIG
174201 id : UUID
@@ -179,6 +206,7 @@ class MeExport(BaseModel):
179206 presets : list [PresetExport ]
180207 images : list [ImageExport ]
181208 submitted_reports : list [ReportExport ]
209+ feedbacks : list [FeedbackExport ]
182210
183211
184212@router .get ("/me/export" , response_model = MeExport )
@@ -202,8 +230,20 @@ async def export_me(
202230 images_result = await db .execute (
203231 select (Image ).where (Image .user_id == user .id ).order_by (Image .created_at )
204232 )
233+ image_rows = images_result .scalars ().all ()
234+
235+ # Edit-States der eigenen Bilder in einer Query (image_id -> state).
236+ edits_by_image : dict [UUID , dict ] = {}
237+ if image_rows :
238+ edit_rows = await db .execute (
239+ select (ImageEdit ).where (
240+ ImageEdit .image_id .in_ ([img .id for img in image_rows ])
241+ )
242+ )
243+ edits_by_image = {e .image_id : e .state for e in edit_rows .scalars ().all ()}
244+
205245 images : list [ImageExport ] = []
206- for img in images_result . scalars (). all () :
246+ for img in image_rows :
207247 url , expires = storage .presign_get (img .bucket_key )
208248 images .append (ImageExport (
209249 id = img .id ,
@@ -215,6 +255,7 @@ async def export_me(
215255 confirmed_at = img .confirmed_at ,
216256 download_url = url ,
217257 download_url_expires_in = expires ,
258+ edit_state = edits_by_image .get (img .id ),
218259 ))
219260
220261 reports_result = await db .execute (
@@ -224,6 +265,15 @@ async def export_me(
224265 )
225266 reports = [ReportExport .model_validate (r ) for r in reports_result .scalars ().all ()]
226267
268+ feedback_result = await db .execute (
269+ select (Feedback )
270+ .where (Feedback .user_id == user .id )
271+ .order_by (Feedback .created_at )
272+ )
273+ feedbacks = [
274+ FeedbackExport .model_validate (f ) for f in feedback_result .scalars ().all ()
275+ ]
276+
227277 return MeExport (
228278 id = user .id ,
229279 email = user .email ,
@@ -233,4 +283,5 @@ async def export_me(
233283 presets = presets ,
234284 images = images ,
235285 submitted_reports = reports ,
286+ feedbacks = feedbacks ,
236287 )
0 commit comments