Commit
·
0183b1e
1
Parent(s):
49b5e1e
Fix empty string handling and support both subCategories and categories schemas
Browse files- Handle empty strings for user_id and category_id properly
- Support both old (subCategories) and new (categories) schemas simultaneously
- Add debug logging for media click tracking
- Preserve existing subCategories data when adding new categories
- Update all endpoints to properly handle empty form fields
- app/database.py +47 -15
- app/main.py +14 -4
- app/main_fastai.py +9 -3
- app/main_sdxl.py +14 -4
- postman_collection.json +100 -0
app/database.py
CHANGED
|
@@ -97,6 +97,10 @@ def _normalize_object_id(raw_value: Optional[Union[str, int, ObjectId]]) -> Obje
|
|
| 97 |
if raw_value is None:
|
| 98 |
return ObjectId()
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
try:
|
| 101 |
if isinstance(raw_value, int) or (isinstance(raw_value, str) and raw_value.strip().lstrip("-").isdigit()):
|
| 102 |
int_value = int(str(raw_value).strip())
|
|
@@ -133,6 +137,10 @@ def _resolve_category_id(
|
|
| 133 |
default_category_id: Optional[str],
|
| 134 |
) -> ObjectId:
|
| 135 |
"""Pick category id from explicit value, endpoint default, or fallback."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
endpoint_map = {
|
| 137 |
"colorization": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
| 138 |
"upload": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
|
@@ -325,8 +333,12 @@ def log_media_click(
|
|
| 325 |
|
| 326 |
user_object_id = _normalize_object_id(user_id)
|
| 327 |
category_object_id = _resolve_category_id(category_id, endpoint_path, default_category_id)
|
| 328 |
-
now = datetime.utcnow()
|
|
|
|
|
|
|
|
|
|
| 329 |
|
|
|
|
| 330 |
update_existing = collection.update_one(
|
| 331 |
{"userId": user_object_id, "categories.categoryId": category_object_id},
|
| 332 |
{
|
|
@@ -336,21 +348,41 @@ def log_media_click(
|
|
| 336 |
)
|
| 337 |
|
| 338 |
if update_existing.matched_count == 0:
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
},
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
|
| 355 |
logger.info("Media click logged for user %s", str(user_object_id))
|
| 356 |
return True
|
|
|
|
| 97 |
if raw_value is None:
|
| 98 |
return ObjectId()
|
| 99 |
|
| 100 |
+
# Handle empty strings as None
|
| 101 |
+
if isinstance(raw_value, str) and not raw_value.strip():
|
| 102 |
+
return ObjectId()
|
| 103 |
+
|
| 104 |
try:
|
| 105 |
if isinstance(raw_value, int) or (isinstance(raw_value, str) and raw_value.strip().lstrip("-").isdigit()):
|
| 106 |
int_value = int(str(raw_value).strip())
|
|
|
|
| 137 |
default_category_id: Optional[str],
|
| 138 |
) -> ObjectId:
|
| 139 |
"""Pick category id from explicit value, endpoint default, or fallback."""
|
| 140 |
+
# Handle empty strings as None
|
| 141 |
+
if isinstance(category_id, str) and not category_id.strip():
|
| 142 |
+
category_id = None
|
| 143 |
+
|
| 144 |
endpoint_map = {
|
| 145 |
"colorization": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
| 146 |
"upload": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
|
|
|
| 333 |
|
| 334 |
user_object_id = _normalize_object_id(user_id)
|
| 335 |
category_object_id = _resolve_category_id(category_id, endpoint_path, default_category_id)
|
| 336 |
+
now = datetime.utcnow()
|
| 337 |
+
|
| 338 |
+
logger.info("Media click - user_id input: %s, normalized: %s, category_id input: %s, normalized: %s",
|
| 339 |
+
user_id, str(user_object_id), category_id, str(category_object_id))
|
| 340 |
|
| 341 |
+
# Try to update existing category in new schema (categories.categoryId)
|
| 342 |
update_existing = collection.update_one(
|
| 343 |
{"userId": user_object_id, "categories.categoryId": category_object_id},
|
| 344 |
{
|
|
|
|
| 348 |
)
|
| 349 |
|
| 350 |
if update_existing.matched_count == 0:
|
| 351 |
+
# Category not found in new schema, check if user exists
|
| 352 |
+
user_exists = collection.find_one({"userId": user_object_id})
|
| 353 |
+
|
| 354 |
+
if user_exists:
|
| 355 |
+
# User exists but category doesn't in new schema - add to categories array
|
| 356 |
+
collection.update_one(
|
| 357 |
+
{"userId": user_object_id},
|
| 358 |
+
{
|
| 359 |
+
"$set": {"updatedAt": now},
|
| 360 |
+
"$push": {
|
| 361 |
+
"categories": {
|
| 362 |
+
"categoryId": category_object_id,
|
| 363 |
+
"click_count": 1,
|
| 364 |
+
"lastClickedAt": now,
|
| 365 |
+
}
|
| 366 |
+
},
|
| 367 |
},
|
| 368 |
+
)
|
| 369 |
+
else:
|
| 370 |
+
# User doesn't exist - create new document with categories array
|
| 371 |
+
collection.update_one(
|
| 372 |
+
{"userId": user_object_id},
|
| 373 |
+
{
|
| 374 |
+
"$setOnInsert": {"createdAt": now},
|
| 375 |
+
"$set": {"updatedAt": now},
|
| 376 |
+
"$push": {
|
| 377 |
+
"categories": {
|
| 378 |
+
"categoryId": category_object_id,
|
| 379 |
+
"click_count": 1,
|
| 380 |
+
"lastClickedAt": now,
|
| 381 |
+
}
|
| 382 |
+
},
|
| 383 |
+
},
|
| 384 |
+
upsert=True,
|
| 385 |
+
)
|
| 386 |
|
| 387 |
logger.info("Media click logged for user %s", str(user_object_id))
|
| 388 |
return True
|
app/main.py
CHANGED
|
@@ -129,8 +129,10 @@ def verify_app_check_token(token: str):
|
|
| 129 |
return True
|
| 130 |
|
| 131 |
def _resolve_user_id(request: Request, supplied_user_id: Optional[str]) -> Optional[str]:
|
| 132 |
-
"""Return supplied user_id if provided, otherwise None (will auto-generate in log_media_click)."""
|
| 133 |
-
|
|
|
|
|
|
|
| 134 |
|
| 135 |
# -------------------------------------------------
|
| 136 |
# 📤 Upload Image
|
|
@@ -148,7 +150,11 @@ async def upload_image(
|
|
| 148 |
|
| 149 |
ip_address = request.client.host if request.client else None
|
| 150 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 151 |
-
effective_category_id = category_id or categoryId
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
if not file.content_type.startswith("image/"):
|
| 154 |
log_api_call(
|
|
@@ -225,7 +231,11 @@ async def colorize(
|
|
| 225 |
|
| 226 |
ip_address = request.client.host if request.client else None
|
| 227 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 228 |
-
effective_category_id = category_id or categoryId
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
if not file.content_type.startswith("image/"):
|
| 231 |
log_api_call(
|
|
|
|
| 129 |
return True
|
| 130 |
|
| 131 |
def _resolve_user_id(request: Request, supplied_user_id: Optional[str]) -> Optional[str]:
|
| 132 |
+
"""Return supplied user_id if provided and not empty, otherwise None (will auto-generate in log_media_click)."""
|
| 133 |
+
if supplied_user_id and supplied_user_id.strip():
|
| 134 |
+
return supplied_user_id.strip()
|
| 135 |
+
return None
|
| 136 |
|
| 137 |
# -------------------------------------------------
|
| 138 |
# 📤 Upload Image
|
|
|
|
| 150 |
|
| 151 |
ip_address = request.client.host if request.client else None
|
| 152 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 153 |
+
effective_category_id = (category_id or categoryId) if (category_id or categoryId) else None
|
| 154 |
+
if effective_category_id:
|
| 155 |
+
effective_category_id = effective_category_id.strip() if isinstance(effective_category_id, str) else effective_category_id
|
| 156 |
+
if not effective_category_id:
|
| 157 |
+
effective_category_id = None
|
| 158 |
|
| 159 |
if not file.content_type.startswith("image/"):
|
| 160 |
log_api_call(
|
|
|
|
| 231 |
|
| 232 |
ip_address = request.client.host if request.client else None
|
| 233 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 234 |
+
effective_category_id = (category_id or categoryId) if (category_id or categoryId) else None
|
| 235 |
+
if effective_category_id:
|
| 236 |
+
effective_category_id = effective_category_id.strip() if isinstance(effective_category_id, str) else effective_category_id
|
| 237 |
+
if not effective_category_id:
|
| 238 |
+
effective_category_id = None
|
| 239 |
|
| 240 |
if not file.content_type.startswith("image/"):
|
| 241 |
log_api_call(
|
app/main_fastai.py
CHANGED
|
@@ -209,8 +209,10 @@ async def verify_request(request: Request):
|
|
| 209 |
|
| 210 |
|
| 211 |
def _resolve_user_id(request: Request, supplied_user_id: Optional[str]) -> Optional[str]:
|
| 212 |
-
"""Return supplied user_id if provided, otherwise None (will auto-generate in log_media_click)."""
|
| 213 |
-
|
|
|
|
|
|
|
| 214 |
|
| 215 |
@app.get("/api")
|
| 216 |
async def api_info(request: Request):
|
|
@@ -382,7 +384,11 @@ async def colorize_api(
|
|
| 382 |
start_time = time.time()
|
| 383 |
|
| 384 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 385 |
-
effective_category_id = category_id or categoryId
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
|
| 387 |
ip_address = request.client.host if request.client else None
|
| 388 |
|
|
|
|
| 209 |
|
| 210 |
|
| 211 |
def _resolve_user_id(request: Request, supplied_user_id: Optional[str]) -> Optional[str]:
|
| 212 |
+
"""Return supplied user_id if provided and not empty, otherwise None (will auto-generate in log_media_click)."""
|
| 213 |
+
if supplied_user_id and supplied_user_id.strip():
|
| 214 |
+
return supplied_user_id.strip()
|
| 215 |
+
return None
|
| 216 |
|
| 217 |
@app.get("/api")
|
| 218 |
async def api_info(request: Request):
|
|
|
|
| 384 |
start_time = time.time()
|
| 385 |
|
| 386 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 387 |
+
effective_category_id = (category_id or categoryId) if (category_id or categoryId) else None
|
| 388 |
+
if effective_category_id:
|
| 389 |
+
effective_category_id = effective_category_id.strip() if isinstance(effective_category_id, str) else effective_category_id
|
| 390 |
+
if not effective_category_id:
|
| 391 |
+
effective_category_id = None
|
| 392 |
|
| 393 |
ip_address = request.client.host if request.client else None
|
| 394 |
|
app/main_sdxl.py
CHANGED
|
@@ -308,8 +308,10 @@ async def verify_request(request: Request):
|
|
| 308 |
|
| 309 |
|
| 310 |
def _resolve_user_id(request: Request, supplied_user_id: Optional[str]) -> Optional[str]:
|
| 311 |
-
"""Return supplied user_id if provided, otherwise None (will auto-generate in log_media_click)."""
|
| 312 |
-
|
|
|
|
|
|
|
| 313 |
|
| 314 |
|
| 315 |
# ========== Auth Endpoints ==========
|
|
@@ -648,7 +650,11 @@ async def upload_image(
|
|
| 648 |
Requires Firebase App Check authentication.
|
| 649 |
"""
|
| 650 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 651 |
-
effective_category_id = category_id or categoryId
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
|
| 653 |
ip_address = request.client.host if request.client else None
|
| 654 |
|
|
@@ -754,7 +760,11 @@ async def colorize_api(
|
|
| 754 |
start_time = time.time()
|
| 755 |
|
| 756 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 757 |
-
effective_category_id = category_id or categoryId
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
|
| 759 |
ip_address = request.client.host if request.client else None
|
| 760 |
|
|
|
|
| 308 |
|
| 309 |
|
| 310 |
def _resolve_user_id(request: Request, supplied_user_id: Optional[str]) -> Optional[str]:
|
| 311 |
+
"""Return supplied user_id if provided and not empty, otherwise None (will auto-generate in log_media_click)."""
|
| 312 |
+
if supplied_user_id and supplied_user_id.strip():
|
| 313 |
+
return supplied_user_id.strip()
|
| 314 |
+
return None
|
| 315 |
|
| 316 |
|
| 317 |
# ========== Auth Endpoints ==========
|
|
|
|
| 650 |
Requires Firebase App Check authentication.
|
| 651 |
"""
|
| 652 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 653 |
+
effective_category_id = (category_id or categoryId) if (category_id or categoryId) else None
|
| 654 |
+
if effective_category_id:
|
| 655 |
+
effective_category_id = effective_category_id.strip() if isinstance(effective_category_id, str) else effective_category_id
|
| 656 |
+
if not effective_category_id:
|
| 657 |
+
effective_category_id = None
|
| 658 |
|
| 659 |
ip_address = request.client.host if request.client else None
|
| 660 |
|
|
|
|
| 760 |
start_time = time.time()
|
| 761 |
|
| 762 |
effective_user_id = _resolve_user_id(request, user_id)
|
| 763 |
+
effective_category_id = (category_id or categoryId) if (category_id or categoryId) else None
|
| 764 |
+
if effective_category_id:
|
| 765 |
+
effective_category_id = effective_category_id.strip() if isinstance(effective_category_id, str) else effective_category_id
|
| 766 |
+
if not effective_category_id:
|
| 767 |
+
effective_category_id = None
|
| 768 |
|
| 769 |
ip_address = request.client.host if request.client else None
|
| 770 |
|
postman_collection.json
CHANGED
|
@@ -78,6 +78,12 @@
|
|
| 78 |
"value": "{{category_id}}",
|
| 79 |
"type": "text",
|
| 80 |
"description": "Optional category id; endpoint default used when omitted"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
]
|
| 83 |
},
|
|
@@ -136,6 +142,12 @@
|
|
| 136 |
"value": "{{category_id}}",
|
| 137 |
"type": "text",
|
| 138 |
"description": "Optional category id; endpoint default used when omitted"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
}
|
| 140 |
]
|
| 141 |
},
|
|
@@ -212,6 +224,12 @@
|
|
| 212 |
"value": "{{category_id}}",
|
| 213 |
"type": "text",
|
| 214 |
"description": "Optional category id; endpoint default used when omitted"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
}
|
| 216 |
]
|
| 217 |
},
|
|
@@ -272,6 +290,64 @@
|
|
| 272 |
},
|
| 273 |
"response": []
|
| 274 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
{
|
| 276 |
"name": "Colorize Image - Without Auth",
|
| 277 |
"request": {
|
|
@@ -331,6 +407,30 @@
|
|
| 331 |
"value": "",
|
| 332 |
"type": "string",
|
| 333 |
"description": "Filename from colorize response (e.g., uuid.png)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
}
|
| 335 |
]
|
| 336 |
}
|
|
|
|
| 78 |
"value": "{{category_id}}",
|
| 79 |
"type": "text",
|
| 80 |
"description": "Optional category id; endpoint default used when omitted"
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"key": "categoryId",
|
| 84 |
+
"value": "{{categoryId}}",
|
| 85 |
+
"type": "text",
|
| 86 |
+
"description": "Optional category id (alternative to category_id)"
|
| 87 |
}
|
| 88 |
]
|
| 89 |
},
|
|
|
|
| 142 |
"value": "{{category_id}}",
|
| 143 |
"type": "text",
|
| 144 |
"description": "Optional category id; endpoint default used when omitted"
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"key": "categoryId",
|
| 148 |
+
"value": "{{categoryId}}",
|
| 149 |
+
"type": "text",
|
| 150 |
+
"description": "Optional category id (alternative to category_id)"
|
| 151 |
}
|
| 152 |
]
|
| 153 |
},
|
|
|
|
| 224 |
"value": "{{category_id}}",
|
| 225 |
"type": "text",
|
| 226 |
"description": "Optional category id; endpoint default used when omitted"
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
"key": "categoryId",
|
| 230 |
+
"value": "{{categoryId}}",
|
| 231 |
+
"type": "text",
|
| 232 |
+
"description": "Optional category id (alternative to category_id)"
|
| 233 |
}
|
| 234 |
]
|
| 235 |
},
|
|
|
|
| 290 |
},
|
| 291 |
"response": []
|
| 292 |
},
|
| 293 |
+
{
|
| 294 |
+
"name": "Upload Image",
|
| 295 |
+
"request": {
|
| 296 |
+
"method": "POST",
|
| 297 |
+
"header": [
|
| 298 |
+
{
|
| 299 |
+
"key": "Authorization",
|
| 300 |
+
"value": "Bearer {{firebase_token}}",
|
| 301 |
+
"type": "text"
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
"key": "X-Firebase-AppCheck",
|
| 305 |
+
"value": "{{firebase_app_check}}",
|
| 306 |
+
"type": "text"
|
| 307 |
+
}
|
| 308 |
+
],
|
| 309 |
+
"body": {
|
| 310 |
+
"mode": "formdata",
|
| 311 |
+
"formdata": [
|
| 312 |
+
{
|
| 313 |
+
"key": "file",
|
| 314 |
+
"type": "file",
|
| 315 |
+
"src": [],
|
| 316 |
+
"description": "Image file to upload"
|
| 317 |
+
},
|
| 318 |
+
{
|
| 319 |
+
"key": "user_id",
|
| 320 |
+
"value": "{{user_id}}",
|
| 321 |
+
"type": "text",
|
| 322 |
+
"description": "Optional user id (ObjectId or numeric) for media click logging"
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"key": "category_id",
|
| 326 |
+
"value": "{{category_id}}",
|
| 327 |
+
"type": "text",
|
| 328 |
+
"description": "Optional category id; endpoint default used when omitted"
|
| 329 |
+
},
|
| 330 |
+
{
|
| 331 |
+
"key": "categoryId",
|
| 332 |
+
"value": "{{categoryId}}",
|
| 333 |
+
"type": "text",
|
| 334 |
+
"description": "Optional category id (alternative to category_id)"
|
| 335 |
+
}
|
| 336 |
+
]
|
| 337 |
+
},
|
| 338 |
+
"url": {
|
| 339 |
+
"raw": "{{base_url}}/upload",
|
| 340 |
+
"host": [
|
| 341 |
+
"{{base_url}}"
|
| 342 |
+
],
|
| 343 |
+
"path": [
|
| 344 |
+
"upload"
|
| 345 |
+
]
|
| 346 |
+
},
|
| 347 |
+
"description": "Upload an image and get the uploaded image URL"
|
| 348 |
+
},
|
| 349 |
+
"response": []
|
| 350 |
+
},
|
| 351 |
{
|
| 352 |
"name": "Colorize Image - Without Auth",
|
| 353 |
"request": {
|
|
|
|
| 407 |
"value": "",
|
| 408 |
"type": "string",
|
| 409 |
"description": "Filename from colorize response (e.g., uuid.png)"
|
| 410 |
+
},
|
| 411 |
+
{
|
| 412 |
+
"key": "user_id",
|
| 413 |
+
"value": "",
|
| 414 |
+
"type": "string",
|
| 415 |
+
"description": "User ID for media click logging (ObjectId string, integer, or leave empty to auto-generate)"
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"key": "category_id",
|
| 419 |
+
"value": "",
|
| 420 |
+
"type": "string",
|
| 421 |
+
"description": "Category ID for media click logging (ObjectId string, or leave empty for endpoint default)"
|
| 422 |
+
},
|
| 423 |
+
{
|
| 424 |
+
"key": "categoryId",
|
| 425 |
+
"value": "",
|
| 426 |
+
"type": "string",
|
| 427 |
+
"description": "Category ID (alternative to category_id)"
|
| 428 |
+
},
|
| 429 |
+
{
|
| 430 |
+
"key": "firebase_app_check",
|
| 431 |
+
"value": "YOUR_FIREBASE_APP_CHECK_TOKEN",
|
| 432 |
+
"type": "string",
|
| 433 |
+
"description": "Firebase App Check token"
|
| 434 |
}
|
| 435 |
]
|
| 436 |
}
|