Commit
·
49b5e1e
1
Parent(s):
7f8c8a6
Add MongoDB media click logging with per-user category counts
Browse files- Add MONGODB_ADMIN connection for admin database
- Implement log_media_click() with user/category normalization
- Support user_id from ObjectId, integer (deterministic), or auto-generate
- Support category_id and categoryId form fields
- Add endpoint defaults for colorization endpoints
- Update /upload and /colorize endpoints in all main files
- Update Postman collection with new fields
- Store data in media_clicks collection with userId, categories array, click_count, and timestamps
- app/config.py +4 -0
- app/database.py +178 -13
- app/main.py +50 -4
- app/main_fastai.py +32 -10
- app/main_sdxl.py +51 -19
- postman_collection.json +51 -0
app/config.py
CHANGED
|
@@ -58,6 +58,10 @@ class Settings(BaseSettings):
|
|
| 58 |
# MONGODB_URI should be set in Hugging Face Space secrets
|
| 59 |
MONGODB_URI: str = os.getenv("MONGODB_URI", "")
|
| 60 |
MONGODB_DB_NAME: str = os.getenv("MONGODB_DB_NAME", "colorization_db")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
class Config:
|
| 63 |
env_file = ".env"
|
|
|
|
| 58 |
# MONGODB_URI should be set in Hugging Face Space secrets
|
| 59 |
MONGODB_URI: str = os.getenv("MONGODB_URI", "")
|
| 60 |
MONGODB_DB_NAME: str = os.getenv("MONGODB_DB_NAME", "colorization_db")
|
| 61 |
+
MONGODB_ADMIN: str = os.getenv("MONGODB_ADMIN", "")
|
| 62 |
+
MONGODB_ADMIN_DB_NAME: str = os.getenv("MONGODB_ADMIN_DB_NAME", None) or os.getenv("MONGODB_DB_NAME", "colorization_db")
|
| 63 |
+
DEFAULT_CATEGORY_COLORIZATION: str = os.getenv("DEFAULT_CATEGORY_COLORIZATION", "")
|
| 64 |
+
DEFAULT_CATEGORY_FALLBACK: str = os.getenv("DEFAULT_CATEGORY_FALLBACK", "69368f722e46bd68ae188984")
|
| 65 |
|
| 66 |
class Config:
|
| 67 |
env_file = ".env"
|
app/database.py
CHANGED
|
@@ -1,18 +1,23 @@
|
|
| 1 |
-
"""
|
| 2 |
-
MongoDB database connection and logging utilities
|
| 3 |
-
"""
|
| 4 |
-
import os
|
| 5 |
-
import logging
|
| 6 |
-
from datetime import datetime
|
| 7 |
-
from typing import Optional, Dict, Any
|
| 8 |
-
from
|
| 9 |
-
from pymongo
|
|
|
|
| 10 |
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
| 13 |
-
# MongoDB connection
|
| 14 |
-
_client: Optional[MongoClient] = None
|
| 15 |
-
_db = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def get_mongodb_client() -> Optional[MongoClient]:
|
| 18 |
"""Get or create MongoDB client"""
|
|
@@ -48,6 +53,103 @@ def get_database():
|
|
| 48 |
logger.warning("MongoDB client not available")
|
| 49 |
return _db
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
def log_api_call(
|
| 52 |
endpoint: str,
|
| 53 |
method: str,
|
|
@@ -198,12 +300,75 @@ def log_colorization(
|
|
| 198 |
logger.error("Failed to log colorization to MongoDB: %s", str(e))
|
| 199 |
return False
|
| 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
def close_connection():
|
| 202 |
"""Close MongoDB connection"""
|
| 203 |
-
global _client, _db
|
| 204 |
if _client:
|
| 205 |
_client.close()
|
| 206 |
_client = None
|
| 207 |
_db = None
|
| 208 |
logger.info("MongoDB connection closed")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MongoDB database connection and logging utilities, including admin media click logging.
|
| 3 |
+
"""
|
| 4 |
+
import os
|
| 5 |
+
import logging
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
from typing import Optional, Dict, Any, Union
|
| 8 |
+
from bson import ObjectId
|
| 9 |
+
from pymongo import MongoClient
|
| 10 |
+
from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError
|
| 11 |
|
| 12 |
logger = logging.getLogger(__name__)
|
| 13 |
|
| 14 |
+
# MongoDB connection
|
| 15 |
+
_client: Optional[MongoClient] = None
|
| 16 |
+
_db = None
|
| 17 |
+
|
| 18 |
+
# Admin MongoDB connection (media_clicks)
|
| 19 |
+
_admin_client: Optional[MongoClient] = None
|
| 20 |
+
_admin_db = None
|
| 21 |
|
| 22 |
def get_mongodb_client() -> Optional[MongoClient]:
|
| 23 |
"""Get or create MongoDB client"""
|
|
|
|
| 53 |
logger.warning("MongoDB client not available")
|
| 54 |
return _db
|
| 55 |
|
| 56 |
+
|
| 57 |
+
def get_admin_client() -> Optional[MongoClient]:
|
| 58 |
+
"""Get or create admin MongoDB client (for media_clicks collection)."""
|
| 59 |
+
global _admin_client
|
| 60 |
+
if _admin_client is None:
|
| 61 |
+
mongodb_uri = os.getenv("MONGODB_ADMIN")
|
| 62 |
+
if not mongodb_uri:
|
| 63 |
+
logger.warning("MONGODB_ADMIN environment variable not set. Admin MongoDB features will be disabled.")
|
| 64 |
+
return None
|
| 65 |
+
try:
|
| 66 |
+
_admin_client = MongoClient(
|
| 67 |
+
mongodb_uri,
|
| 68 |
+
serverSelectionTimeoutMS=5000,
|
| 69 |
+
connectTimeoutMS=5000,
|
| 70 |
+
)
|
| 71 |
+
_admin_client.admin.command("ping")
|
| 72 |
+
logger.info("Admin MongoDB connection established successfully")
|
| 73 |
+
except (ConnectionFailure, ServerSelectionTimeoutError) as exc:
|
| 74 |
+
logger.error("Failed to connect to Admin MongoDB: %s", str(exc))
|
| 75 |
+
_admin_client = None
|
| 76 |
+
return _admin_client
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def get_admin_database():
|
| 80 |
+
"""Get admin database instance."""
|
| 81 |
+
global _admin_db
|
| 82 |
+
if _admin_db is None:
|
| 83 |
+
client = get_admin_client()
|
| 84 |
+
if client:
|
| 85 |
+
db_name = os.getenv("MONGODB_ADMIN_DB_NAME", os.getenv("MONGODB_DB_NAME", "colorization_db"))
|
| 86 |
+
_admin_db = client[db_name]
|
| 87 |
+
else:
|
| 88 |
+
logger.warning("Admin MongoDB client not available")
|
| 89 |
+
return _admin_db
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _normalize_object_id(raw_value: Optional[Union[str, int, ObjectId]]) -> ObjectId:
|
| 93 |
+
"""Normalize user id inputs into a deterministic ObjectId."""
|
| 94 |
+
if isinstance(raw_value, ObjectId):
|
| 95 |
+
return raw_value
|
| 96 |
+
|
| 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())
|
| 103 |
+
hex_str = format(abs(int_value), "x").zfill(24)[-24:]
|
| 104 |
+
if ObjectId.is_valid(hex_str):
|
| 105 |
+
return ObjectId(hex_str)
|
| 106 |
+
except Exception as exc: # pragma: no cover - defensive
|
| 107 |
+
logger.debug("Numeric user id normalization failed: %s", str(exc))
|
| 108 |
+
|
| 109 |
+
if isinstance(raw_value, str):
|
| 110 |
+
candidate = raw_value.strip()
|
| 111 |
+
if ObjectId.is_valid(candidate):
|
| 112 |
+
return ObjectId(candidate)
|
| 113 |
+
|
| 114 |
+
return ObjectId()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _objectid_from_any(value: str) -> ObjectId:
|
| 118 |
+
"""Convert arbitrary string into an ObjectId deterministically when possible."""
|
| 119 |
+
if ObjectId.is_valid(value):
|
| 120 |
+
return ObjectId(value)
|
| 121 |
+
try:
|
| 122 |
+
hex_str = value.encode("utf-8").hex().zfill(24)[-24:]
|
| 123 |
+
if ObjectId.is_valid(hex_str):
|
| 124 |
+
return ObjectId(hex_str)
|
| 125 |
+
except Exception as exc: # pragma: no cover - defensive
|
| 126 |
+
logger.debug("Category id normalization failed: %s", str(exc))
|
| 127 |
+
return ObjectId()
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def _resolve_category_id(
|
| 131 |
+
category_id: Optional[str],
|
| 132 |
+
endpoint_path: Optional[str],
|
| 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"),
|
| 139 |
+
"colorize": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
| 140 |
+
}
|
| 141 |
+
normalized_endpoint = None
|
| 142 |
+
if endpoint_path:
|
| 143 |
+
normalized_endpoint = endpoint_path.strip("/").split("/")[0].lower() or None
|
| 144 |
+
|
| 145 |
+
chosen = category_id
|
| 146 |
+
if not chosen and normalized_endpoint and endpoint_map.get(normalized_endpoint):
|
| 147 |
+
chosen = endpoint_map[normalized_endpoint]
|
| 148 |
+
if not chosen:
|
| 149 |
+
chosen = default_category_id or os.getenv("DEFAULT_CATEGORY_FALLBACK", "69368f722e46bd68ae188984")
|
| 150 |
+
|
| 151 |
+
return _objectid_from_any(chosen)
|
| 152 |
+
|
| 153 |
def log_api_call(
|
| 154 |
endpoint: str,
|
| 155 |
method: str,
|
|
|
|
| 300 |
logger.error("Failed to log colorization to MongoDB: %s", str(e))
|
| 301 |
return False
|
| 302 |
|
| 303 |
+
|
| 304 |
+
def log_media_click(
|
| 305 |
+
user_id: Optional[Union[str, int, ObjectId]],
|
| 306 |
+
category_id: Optional[str],
|
| 307 |
+
*,
|
| 308 |
+
endpoint_path: Optional[str] = None,
|
| 309 |
+
default_category_id: Optional[str] = None,
|
| 310 |
+
) -> bool:
|
| 311 |
+
"""Log media clicks into the admin MongoDB (media_clicks collection)."""
|
| 312 |
+
try:
|
| 313 |
+
db = get_admin_database()
|
| 314 |
+
if db is None:
|
| 315 |
+
logger.warning("Admin MongoDB not available, skipping media click log")
|
| 316 |
+
return False
|
| 317 |
+
|
| 318 |
+
collection = db["media_clicks"]
|
| 319 |
+
|
| 320 |
+
# Drop legacy index to avoid duplicate key errors (best effort)
|
| 321 |
+
try:
|
| 322 |
+
collection.drop_index("user_id_1_header_1_media_id_1")
|
| 323 |
+
except Exception as exc:
|
| 324 |
+
logger.debug("Legacy index drop skipped: %s", str(exc))
|
| 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 |
+
{
|
| 333 |
+
"$inc": {"categories.$.click_count": 1},
|
| 334 |
+
"$set": {"categories.$.lastClickedAt": now, "updatedAt": now},
|
| 335 |
+
},
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
if update_existing.matched_count == 0:
|
| 339 |
+
collection.update_one(
|
| 340 |
+
{"userId": user_object_id},
|
| 341 |
+
{
|
| 342 |
+
"$setOnInsert": {"createdAt": now},
|
| 343 |
+
"$set": {"updatedAt": now},
|
| 344 |
+
"$push": {
|
| 345 |
+
"categories": {
|
| 346 |
+
"categoryId": category_object_id,
|
| 347 |
+
"click_count": 1,
|
| 348 |
+
"lastClickedAt": now,
|
| 349 |
+
}
|
| 350 |
+
},
|
| 351 |
+
},
|
| 352 |
+
upsert=True,
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
logger.info("Media click logged for user %s", str(user_object_id))
|
| 356 |
+
return True
|
| 357 |
+
except Exception as exc:
|
| 358 |
+
logger.error("Failed to log media click to admin MongoDB: %s", str(exc))
|
| 359 |
+
return False
|
| 360 |
+
|
| 361 |
def close_connection():
|
| 362 |
"""Close MongoDB connection"""
|
| 363 |
+
global _client, _db, _admin_client, _admin_db
|
| 364 |
if _client:
|
| 365 |
_client.close()
|
| 366 |
_client = None
|
| 367 |
_db = None
|
| 368 |
logger.info("MongoDB connection closed")
|
| 369 |
+
if _admin_client:
|
| 370 |
+
_admin_client.close()
|
| 371 |
+
_admin_client = None
|
| 372 |
+
_admin_db = None
|
| 373 |
+
logger.info("Admin MongoDB connection closed")
|
| 374 |
|
app/main.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
-
from fastapi import FastAPI, File, UploadFile, HTTPException, Header, Request
|
|
|
|
| 2 |
from fastapi.responses import FileResponse
|
| 3 |
from huggingface_hub import hf_hub_download
|
| 4 |
import uuid
|
|
@@ -8,7 +9,18 @@ import json
|
|
| 8 |
from PIL import Image
|
| 9 |
import torch
|
| 10 |
from torchvision import transforms
|
| 11 |
-
from app.database import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
# -------------------------------------------------
|
| 14 |
# 🚀 FastAPI App
|
|
@@ -43,6 +55,8 @@ RESULTS_DIR = "/tmp/results"
|
|
| 43 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 44 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 45 |
|
|
|
|
|
|
|
| 46 |
# -------------------------------------------------
|
| 47 |
# 🧠 Load GAN Colorization Model
|
| 48 |
# -------------------------------------------------
|
|
@@ -114,6 +128,10 @@ def verify_app_check_token(token: str):
|
|
| 114 |
raise HTTPException(status_code=401, detail="Invalid Firebase App Check token")
|
| 115 |
return True
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
# -------------------------------------------------
|
| 118 |
# 📤 Upload Image
|
| 119 |
# -------------------------------------------------
|
|
@@ -121,11 +139,16 @@ def verify_app_check_token(token: str):
|
|
| 121 |
async def upload_image(
|
| 122 |
request: Request,
|
| 123 |
file: UploadFile = File(...),
|
| 124 |
-
x_firebase_appcheck: str = Header(None)
|
|
|
|
|
|
|
|
|
|
| 125 |
):
|
| 126 |
verify_app_check_token(x_firebase_appcheck)
|
| 127 |
|
| 128 |
ip_address = request.client.host if request.client else None
|
|
|
|
|
|
|
| 129 |
|
| 130 |
if not file.content_type.startswith("image/"):
|
| 131 |
log_api_call(
|
|
@@ -160,6 +183,7 @@ async def upload_image(
|
|
| 160 |
filename=file.filename or image_id,
|
| 161 |
file_size=file_size,
|
| 162 |
content_type=file.content_type or "image/jpeg",
|
|
|
|
| 163 |
ip_address=ip_address
|
| 164 |
)
|
| 165 |
|
|
@@ -169,9 +193,17 @@ async def upload_image(
|
|
| 169 |
status_code=200,
|
| 170 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 171 |
response_data=response_data,
|
|
|
|
| 172 |
ip_address=ip_address
|
| 173 |
)
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
return response_data
|
| 176 |
|
| 177 |
# -------------------------------------------------
|
|
@@ -181,7 +213,10 @@ async def upload_image(
|
|
| 181 |
async def colorize(
|
| 182 |
request: Request,
|
| 183 |
file: UploadFile = File(...),
|
| 184 |
-
x_firebase_appcheck: str = Header(None)
|
|
|
|
|
|
|
|
|
|
| 185 |
):
|
| 186 |
import time
|
| 187 |
start_time = time.time()
|
|
@@ -189,6 +224,8 @@ async def colorize(
|
|
| 189 |
verify_app_check_token(x_firebase_appcheck)
|
| 190 |
|
| 191 |
ip_address = request.client.host if request.client else None
|
|
|
|
|
|
|
| 192 |
|
| 193 |
if not file.content_type.startswith("image/"):
|
| 194 |
log_api_call(
|
|
@@ -225,6 +262,7 @@ async def colorize(
|
|
| 225 |
result_id=result_id_clean,
|
| 226 |
model_type="gan",
|
| 227 |
processing_time=processing_time,
|
|
|
|
| 228 |
ip_address=ip_address
|
| 229 |
)
|
| 230 |
|
|
@@ -234,9 +272,17 @@ async def colorize(
|
|
| 234 |
status_code=200,
|
| 235 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 236 |
response_data=response_data,
|
|
|
|
| 237 |
ip_address=ip_address
|
| 238 |
)
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
return response_data
|
| 241 |
|
| 242 |
# -------------------------------------------------
|
|
|
|
| 1 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException, Header, Request, Form
|
| 2 |
+
from typing import Optional
|
| 3 |
from fastapi.responses import FileResponse
|
| 4 |
from huggingface_hub import hf_hub_download
|
| 5 |
import uuid
|
|
|
|
| 9 |
from PIL import Image
|
| 10 |
import torch
|
| 11 |
from torchvision import transforms
|
| 12 |
+
from app.database import (
|
| 13 |
+
get_database,
|
| 14 |
+
log_api_call,
|
| 15 |
+
log_image_upload,
|
| 16 |
+
log_colorization,
|
| 17 |
+
log_media_click,
|
| 18 |
+
close_connection,
|
| 19 |
+
)
|
| 20 |
+
try:
|
| 21 |
+
from firebase_admin import auth as firebase_auth
|
| 22 |
+
except ImportError:
|
| 23 |
+
firebase_auth = None
|
| 24 |
|
| 25 |
# -------------------------------------------------
|
| 26 |
# 🚀 FastAPI App
|
|
|
|
| 55 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 56 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 57 |
|
| 58 |
+
MEDIA_CLICK_DEFAULT_CATEGORY = os.getenv("DEFAULT_CATEGORY_FALLBACK", "69368f722e46bd68ae188984")
|
| 59 |
+
|
| 60 |
# -------------------------------------------------
|
| 61 |
# 🧠 Load GAN Colorization Model
|
| 62 |
# -------------------------------------------------
|
|
|
|
| 128 |
raise HTTPException(status_code=401, detail="Invalid Firebase App Check token")
|
| 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 |
+
return supplied_user_id
|
| 134 |
+
|
| 135 |
# -------------------------------------------------
|
| 136 |
# 📤 Upload Image
|
| 137 |
# -------------------------------------------------
|
|
|
|
| 139 |
async def upload_image(
|
| 140 |
request: Request,
|
| 141 |
file: UploadFile = File(...),
|
| 142 |
+
x_firebase_appcheck: str = Header(None),
|
| 143 |
+
user_id: Optional[str] = Form(None),
|
| 144 |
+
category_id: Optional[str] = Form(None),
|
| 145 |
+
categoryId: Optional[str] = Form(None),
|
| 146 |
):
|
| 147 |
verify_app_check_token(x_firebase_appcheck)
|
| 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(
|
|
|
|
| 183 |
filename=file.filename or image_id,
|
| 184 |
file_size=file_size,
|
| 185 |
content_type=file.content_type or "image/jpeg",
|
| 186 |
+
user_id=effective_user_id,
|
| 187 |
ip_address=ip_address
|
| 188 |
)
|
| 189 |
|
|
|
|
| 193 |
status_code=200,
|
| 194 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 195 |
response_data=response_data,
|
| 196 |
+
user_id=effective_user_id,
|
| 197 |
ip_address=ip_address
|
| 198 |
)
|
| 199 |
|
| 200 |
+
log_media_click(
|
| 201 |
+
user_id=effective_user_id,
|
| 202 |
+
category_id=effective_category_id,
|
| 203 |
+
endpoint_path=str(request.url.path),
|
| 204 |
+
default_category_id=MEDIA_CLICK_DEFAULT_CATEGORY,
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
return response_data
|
| 208 |
|
| 209 |
# -------------------------------------------------
|
|
|
|
| 213 |
async def colorize(
|
| 214 |
request: Request,
|
| 215 |
file: UploadFile = File(...),
|
| 216 |
+
x_firebase_appcheck: str = Header(None),
|
| 217 |
+
user_id: Optional[str] = Form(None),
|
| 218 |
+
category_id: Optional[str] = Form(None),
|
| 219 |
+
categoryId: Optional[str] = Form(None),
|
| 220 |
):
|
| 221 |
import time
|
| 222 |
start_time = time.time()
|
|
|
|
| 224 |
verify_app_check_token(x_firebase_appcheck)
|
| 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(
|
|
|
|
| 262 |
result_id=result_id_clean,
|
| 263 |
model_type="gan",
|
| 264 |
processing_time=processing_time,
|
| 265 |
+
user_id=effective_user_id,
|
| 266 |
ip_address=ip_address
|
| 267 |
)
|
| 268 |
|
|
|
|
| 272 |
status_code=200,
|
| 273 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 274 |
response_data=response_data,
|
| 275 |
+
user_id=effective_user_id,
|
| 276 |
ip_address=ip_address
|
| 277 |
)
|
| 278 |
|
| 279 |
+
log_media_click(
|
| 280 |
+
user_id=effective_user_id,
|
| 281 |
+
category_id=effective_category_id,
|
| 282 |
+
endpoint_path=str(request.url.path),
|
| 283 |
+
default_category_id=MEDIA_CLICK_DEFAULT_CATEGORY,
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
return response_data
|
| 287 |
|
| 288 |
# -------------------------------------------------
|
app/main_fastai.py
CHANGED
|
@@ -18,7 +18,7 @@ import logging
|
|
| 18 |
from pathlib import Path
|
| 19 |
from typing import Optional
|
| 20 |
|
| 21 |
-
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, Request
|
| 22 |
from fastapi.responses import FileResponse, JSONResponse
|
| 23 |
from fastapi.middleware.cors import CORSMiddleware
|
| 24 |
from fastapi.staticfiles import StaticFiles
|
|
@@ -37,7 +37,14 @@ from huggingface_hub import from_pretrained_fastai
|
|
| 37 |
|
| 38 |
from app.config import settings
|
| 39 |
from app.pytorch_colorizer import PyTorchColorizer
|
| 40 |
-
from app.database import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
# Configure logging
|
| 43 |
logging.basicConfig(
|
|
@@ -200,6 +207,11 @@ async def verify_request(request: Request):
|
|
| 200 |
# Neither token required nor provided → allow (App Check disabled)
|
| 201 |
return True
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
@app.get("/api")
|
| 204 |
async def api_info(request: Request):
|
| 205 |
"""API info endpoint"""
|
|
@@ -357,6 +369,9 @@ def colorize_pil(image: Image.Image) -> Image.Image:
|
|
| 357 |
async def colorize_api(
|
| 358 |
request: Request,
|
| 359 |
file: UploadFile = File(...),
|
|
|
|
|
|
|
|
|
|
| 360 |
verified: bool = Depends(verify_request)
|
| 361 |
):
|
| 362 |
"""
|
|
@@ -366,10 +381,9 @@ async def colorize_api(
|
|
| 366 |
import time
|
| 367 |
start_time = time.time()
|
| 368 |
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
ip_address = request.client.host if request.client else None
|
| 374 |
|
| 375 |
# Allow fallback colorization even if model isn't loaded
|
|
@@ -382,7 +396,7 @@ async def colorize_api(
|
|
| 382 |
method="POST",
|
| 383 |
status_code=400,
|
| 384 |
error="File must be an image",
|
| 385 |
-
user_id=
|
| 386 |
ip_address=ip_address
|
| 387 |
)
|
| 388 |
raise HTTPException(status_code=400, detail="File must be an image")
|
|
@@ -409,7 +423,7 @@ async def colorize_api(
|
|
| 409 |
result_id=result_id,
|
| 410 |
model_type=model_type,
|
| 411 |
processing_time=processing_time,
|
| 412 |
-
user_id=
|
| 413 |
ip_address=ip_address
|
| 414 |
)
|
| 415 |
|
|
@@ -419,9 +433,17 @@ async def colorize_api(
|
|
| 419 |
status_code=200,
|
| 420 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 421 |
response_data={"result_id": result_id, "filename": output_filename},
|
| 422 |
-
user_id=
|
| 423 |
ip_address=ip_address
|
| 424 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
# Return the image file
|
| 427 |
return FileResponse(
|
|
@@ -437,7 +459,7 @@ async def colorize_api(
|
|
| 437 |
method="POST",
|
| 438 |
status_code=500,
|
| 439 |
error=error_msg,
|
| 440 |
-
user_id=
|
| 441 |
ip_address=ip_address
|
| 442 |
)
|
| 443 |
raise HTTPException(status_code=500, detail=f"Error colorizing image: {error_msg}")
|
|
|
|
| 18 |
from pathlib import Path
|
| 19 |
from typing import Optional
|
| 20 |
|
| 21 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, Request, Form
|
| 22 |
from fastapi.responses import FileResponse, JSONResponse
|
| 23 |
from fastapi.middleware.cors import CORSMiddleware
|
| 24 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
| 37 |
|
| 38 |
from app.config import settings
|
| 39 |
from app.pytorch_colorizer import PyTorchColorizer
|
| 40 |
+
from app.database import (
|
| 41 |
+
get_database,
|
| 42 |
+
log_api_call,
|
| 43 |
+
log_image_upload,
|
| 44 |
+
log_colorization,
|
| 45 |
+
log_media_click,
|
| 46 |
+
close_connection,
|
| 47 |
+
)
|
| 48 |
|
| 49 |
# Configure logging
|
| 50 |
logging.basicConfig(
|
|
|
|
| 207 |
# Neither token required nor provided → allow (App Check disabled)
|
| 208 |
return True
|
| 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 |
+
return supplied_user_id
|
| 214 |
+
|
| 215 |
@app.get("/api")
|
| 216 |
async def api_info(request: Request):
|
| 217 |
"""API info endpoint"""
|
|
|
|
| 369 |
async def colorize_api(
|
| 370 |
request: Request,
|
| 371 |
file: UploadFile = File(...),
|
| 372 |
+
user_id: Optional[str] = Form(None),
|
| 373 |
+
category_id: Optional[str] = Form(None),
|
| 374 |
+
categoryId: Optional[str] = Form(None),
|
| 375 |
verified: bool = Depends(verify_request)
|
| 376 |
):
|
| 377 |
"""
|
|
|
|
| 381 |
import time
|
| 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 |
|
| 389 |
# Allow fallback colorization even if model isn't loaded
|
|
|
|
| 396 |
method="POST",
|
| 397 |
status_code=400,
|
| 398 |
error="File must be an image",
|
| 399 |
+
user_id=effective_user_id,
|
| 400 |
ip_address=ip_address
|
| 401 |
)
|
| 402 |
raise HTTPException(status_code=400, detail="File must be an image")
|
|
|
|
| 423 |
result_id=result_id,
|
| 424 |
model_type=model_type,
|
| 425 |
processing_time=processing_time,
|
| 426 |
+
user_id=effective_user_id,
|
| 427 |
ip_address=ip_address
|
| 428 |
)
|
| 429 |
|
|
|
|
| 433 |
status_code=200,
|
| 434 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 435 |
response_data={"result_id": result_id, "filename": output_filename},
|
| 436 |
+
user_id=effective_user_id,
|
| 437 |
ip_address=ip_address
|
| 438 |
)
|
| 439 |
+
|
| 440 |
+
# Best-effort media click tracking (admin DB)
|
| 441 |
+
log_media_click(
|
| 442 |
+
user_id=effective_user_id,
|
| 443 |
+
category_id=effective_category_id,
|
| 444 |
+
endpoint_path=str(request.url.path),
|
| 445 |
+
default_category_id=settings.DEFAULT_CATEGORY_FALLBACK,
|
| 446 |
+
)
|
| 447 |
|
| 448 |
# Return the image file
|
| 449 |
return FileResponse(
|
|
|
|
| 459 |
method="POST",
|
| 460 |
status_code=500,
|
| 461 |
error=error_msg,
|
| 462 |
+
user_id=effective_user_id,
|
| 463 |
ip_address=ip_address
|
| 464 |
)
|
| 465 |
raise HTTPException(status_code=500, detail=f"Error colorizing image: {error_msg}")
|
app/main_sdxl.py
CHANGED
|
@@ -9,7 +9,7 @@ import logging
|
|
| 9 |
from pathlib import Path
|
| 10 |
from typing import Optional, Tuple
|
| 11 |
|
| 12 |
-
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, Request, Body
|
| 13 |
from fastapi.responses import FileResponse, JSONResponse
|
| 14 |
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
from fastapi.staticfiles import StaticFiles
|
|
@@ -25,7 +25,14 @@ from pydantic import BaseModel, EmailStr
|
|
| 25 |
from huggingface_hub import InferenceClient
|
| 26 |
|
| 27 |
from app.config import settings
|
| 28 |
-
from app.database import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
# Configure logging
|
| 31 |
logging.basicConfig(
|
|
@@ -300,6 +307,11 @@ async def verify_request(request: Request):
|
|
| 300 |
return True
|
| 301 |
|
| 302 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
# ========== Auth Endpoints ==========
|
| 304 |
|
| 305 |
@app.post("/auth/register", response_model=TokenResponse)
|
|
@@ -626,16 +638,18 @@ def colorize_image_sdxl(
|
|
| 626 |
async def upload_image(
|
| 627 |
request: Request,
|
| 628 |
file: UploadFile = File(...),
|
|
|
|
|
|
|
|
|
|
| 629 |
verified: bool = Depends(verify_request)
|
| 630 |
):
|
| 631 |
"""
|
| 632 |
Upload an image and get the uploaded image URL.
|
| 633 |
Requires Firebase App Check authentication.
|
| 634 |
"""
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
ip_address = request.client.host if request.client else None
|
| 640 |
|
| 641 |
if not file.content_type or not file.content_type.startswith("image/"):
|
|
@@ -644,7 +658,7 @@ async def upload_image(
|
|
| 644 |
method="POST",
|
| 645 |
status_code=400,
|
| 646 |
error="File must be an image",
|
| 647 |
-
user_id=
|
| 648 |
ip_address=ip_address
|
| 649 |
)
|
| 650 |
raise HTTPException(status_code=400, detail="File must be an image")
|
|
@@ -682,7 +696,7 @@ async def upload_image(
|
|
| 682 |
filename=file.filename or image_id,
|
| 683 |
file_size=file_size,
|
| 684 |
content_type=file.content_type or "image/jpeg",
|
| 685 |
-
user_id=
|
| 686 |
ip_address=ip_address
|
| 687 |
)
|
| 688 |
|
|
@@ -692,9 +706,17 @@ async def upload_image(
|
|
| 692 |
status_code=200,
|
| 693 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 694 |
response_data=response_data,
|
| 695 |
-
user_id=
|
| 696 |
ip_address=ip_address
|
| 697 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 698 |
|
| 699 |
return JSONResponse(response_data)
|
| 700 |
except Exception as e:
|
|
@@ -705,7 +727,7 @@ async def upload_image(
|
|
| 705 |
method="POST",
|
| 706 |
status_code=500,
|
| 707 |
error=error_msg,
|
| 708 |
-
user_id=
|
| 709 |
ip_address=ip_address
|
| 710 |
)
|
| 711 |
raise HTTPException(status_code=500, detail=f"Error uploading image: {error_msg}")
|
|
@@ -719,6 +741,9 @@ async def colorize_api(
|
|
| 719 |
negative_prompt: Optional[str] = None,
|
| 720 |
seed: int = 123,
|
| 721 |
num_inference_steps: int = 8,
|
|
|
|
|
|
|
|
|
|
| 722 |
verified: bool = Depends(verify_request)
|
| 723 |
):
|
| 724 |
"""
|
|
@@ -728,10 +753,9 @@ async def colorize_api(
|
|
| 728 |
import time
|
| 729 |
start_time = time.time()
|
| 730 |
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
ip_address = request.client.host if request.client else None
|
| 736 |
|
| 737 |
if inference_client is None:
|
|
@@ -740,7 +764,7 @@ async def colorize_api(
|
|
| 740 |
method="POST",
|
| 741 |
status_code=503,
|
| 742 |
error="Inference API client not initialized",
|
| 743 |
-
user_id=
|
| 744 |
ip_address=ip_address
|
| 745 |
)
|
| 746 |
raise HTTPException(status_code=503, detail="Inference API client not initialized")
|
|
@@ -751,7 +775,7 @@ async def colorize_api(
|
|
| 751 |
method="POST",
|
| 752 |
status_code=400,
|
| 753 |
error="File must be an image",
|
| 754 |
-
user_id=
|
| 755 |
ip_address=ip_address
|
| 756 |
)
|
| 757 |
raise HTTPException(status_code=400, detail="File must be an image")
|
|
@@ -799,7 +823,7 @@ async def colorize_api(
|
|
| 799 |
prompt=positive_prompt,
|
| 800 |
model_type="sdxl",
|
| 801 |
processing_time=processing_time,
|
| 802 |
-
user_id=
|
| 803 |
ip_address=ip_address
|
| 804 |
)
|
| 805 |
|
|
@@ -815,9 +839,17 @@ async def colorize_api(
|
|
| 815 |
"num_inference_steps": num_inference_steps
|
| 816 |
},
|
| 817 |
response_data=response_data,
|
| 818 |
-
user_id=
|
| 819 |
ip_address=ip_address
|
| 820 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 821 |
|
| 822 |
return JSONResponse(response_data)
|
| 823 |
except Exception as e:
|
|
@@ -828,7 +860,7 @@ async def colorize_api(
|
|
| 828 |
method="POST",
|
| 829 |
status_code=500,
|
| 830 |
error=error_msg,
|
| 831 |
-
user_id=
|
| 832 |
ip_address=ip_address
|
| 833 |
)
|
| 834 |
raise HTTPException(status_code=500, detail=f"Error colorizing image: {error_msg}")
|
|
|
|
| 9 |
from pathlib import Path
|
| 10 |
from typing import Optional, Tuple
|
| 11 |
|
| 12 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, Request, Body, Form
|
| 13 |
from fastapi.responses import FileResponse, JSONResponse
|
| 14 |
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
| 25 |
from huggingface_hub import InferenceClient
|
| 26 |
|
| 27 |
from app.config import settings
|
| 28 |
+
from app.database import (
|
| 29 |
+
get_database,
|
| 30 |
+
log_api_call,
|
| 31 |
+
log_image_upload,
|
| 32 |
+
log_colorization,
|
| 33 |
+
log_media_click,
|
| 34 |
+
close_connection,
|
| 35 |
+
)
|
| 36 |
|
| 37 |
# Configure logging
|
| 38 |
logging.basicConfig(
|
|
|
|
| 307 |
return True
|
| 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 |
+
return supplied_user_id
|
| 313 |
+
|
| 314 |
+
|
| 315 |
# ========== Auth Endpoints ==========
|
| 316 |
|
| 317 |
@app.post("/auth/register", response_model=TokenResponse)
|
|
|
|
| 638 |
async def upload_image(
|
| 639 |
request: Request,
|
| 640 |
file: UploadFile = File(...),
|
| 641 |
+
user_id: Optional[str] = Form(None),
|
| 642 |
+
category_id: Optional[str] = Form(None),
|
| 643 |
+
categoryId: Optional[str] = Form(None),
|
| 644 |
verified: bool = Depends(verify_request)
|
| 645 |
):
|
| 646 |
"""
|
| 647 |
Upload an image and get the uploaded image URL.
|
| 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 |
|
| 655 |
if not file.content_type or not file.content_type.startswith("image/"):
|
|
|
|
| 658 |
method="POST",
|
| 659 |
status_code=400,
|
| 660 |
error="File must be an image",
|
| 661 |
+
user_id=effective_user_id,
|
| 662 |
ip_address=ip_address
|
| 663 |
)
|
| 664 |
raise HTTPException(status_code=400, detail="File must be an image")
|
|
|
|
| 696 |
filename=file.filename or image_id,
|
| 697 |
file_size=file_size,
|
| 698 |
content_type=file.content_type or "image/jpeg",
|
| 699 |
+
user_id=effective_user_id,
|
| 700 |
ip_address=ip_address
|
| 701 |
)
|
| 702 |
|
|
|
|
| 706 |
status_code=200,
|
| 707 |
request_data={"filename": file.filename, "content_type": file.content_type},
|
| 708 |
response_data=response_data,
|
| 709 |
+
user_id=effective_user_id,
|
| 710 |
ip_address=ip_address
|
| 711 |
)
|
| 712 |
+
|
| 713 |
+
# Best-effort media click tracking (admin DB)
|
| 714 |
+
log_media_click(
|
| 715 |
+
user_id=effective_user_id,
|
| 716 |
+
category_id=effective_category_id,
|
| 717 |
+
endpoint_path=str(request.url.path),
|
| 718 |
+
default_category_id=settings.DEFAULT_CATEGORY_FALLBACK,
|
| 719 |
+
)
|
| 720 |
|
| 721 |
return JSONResponse(response_data)
|
| 722 |
except Exception as e:
|
|
|
|
| 727 |
method="POST",
|
| 728 |
status_code=500,
|
| 729 |
error=error_msg,
|
| 730 |
+
user_id=effective_user_id,
|
| 731 |
ip_address=ip_address
|
| 732 |
)
|
| 733 |
raise HTTPException(status_code=500, detail=f"Error uploading image: {error_msg}")
|
|
|
|
| 741 |
negative_prompt: Optional[str] = None,
|
| 742 |
seed: int = 123,
|
| 743 |
num_inference_steps: int = 8,
|
| 744 |
+
user_id: Optional[str] = Form(None),
|
| 745 |
+
category_id: Optional[str] = Form(None),
|
| 746 |
+
categoryId: Optional[str] = Form(None),
|
| 747 |
verified: bool = Depends(verify_request)
|
| 748 |
):
|
| 749 |
"""
|
|
|
|
| 753 |
import time
|
| 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 |
|
| 761 |
if inference_client is None:
|
|
|
|
| 764 |
method="POST",
|
| 765 |
status_code=503,
|
| 766 |
error="Inference API client not initialized",
|
| 767 |
+
user_id=effective_user_id,
|
| 768 |
ip_address=ip_address
|
| 769 |
)
|
| 770 |
raise HTTPException(status_code=503, detail="Inference API client not initialized")
|
|
|
|
| 775 |
method="POST",
|
| 776 |
status_code=400,
|
| 777 |
error="File must be an image",
|
| 778 |
+
user_id=effective_user_id,
|
| 779 |
ip_address=ip_address
|
| 780 |
)
|
| 781 |
raise HTTPException(status_code=400, detail="File must be an image")
|
|
|
|
| 823 |
prompt=positive_prompt,
|
| 824 |
model_type="sdxl",
|
| 825 |
processing_time=processing_time,
|
| 826 |
+
user_id=effective_user_id,
|
| 827 |
ip_address=ip_address
|
| 828 |
)
|
| 829 |
|
|
|
|
| 839 |
"num_inference_steps": num_inference_steps
|
| 840 |
},
|
| 841 |
response_data=response_data,
|
| 842 |
+
user_id=effective_user_id,
|
| 843 |
ip_address=ip_address
|
| 844 |
)
|
| 845 |
+
|
| 846 |
+
# Best-effort media click tracking (admin DB)
|
| 847 |
+
log_media_click(
|
| 848 |
+
user_id=effective_user_id,
|
| 849 |
+
category_id=effective_category_id,
|
| 850 |
+
endpoint_path=str(request.url.path),
|
| 851 |
+
default_category_id=settings.DEFAULT_CATEGORY_FALLBACK,
|
| 852 |
+
)
|
| 853 |
|
| 854 |
return JSONResponse(response_data)
|
| 855 |
except Exception as e:
|
|
|
|
| 860 |
method="POST",
|
| 861 |
status_code=500,
|
| 862 |
error=error_msg,
|
| 863 |
+
user_id=effective_user_id,
|
| 864 |
ip_address=ip_address
|
| 865 |
)
|
| 866 |
raise HTTPException(status_code=500, detail=f"Error colorizing image: {error_msg}")
|
postman_collection.json
CHANGED
|
@@ -51,6 +51,11 @@
|
|
| 51 |
"key": "Authorization",
|
| 52 |
"value": "Bearer {{firebase_token}}",
|
| 53 |
"type": "text"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
],
|
| 56 |
"body": {
|
|
@@ -61,6 +66,18 @@
|
|
| 61 |
"type": "file",
|
| 62 |
"src": [],
|
| 63 |
"description": "Image file to colorize"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
]
|
| 66 |
},
|
|
@@ -86,6 +103,11 @@
|
|
| 86 |
"key": "Authorization",
|
| 87 |
"value": "Bearer {{firebase_token}}",
|
| 88 |
"type": "text"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
],
|
| 91 |
"body": {
|
|
@@ -102,6 +124,18 @@
|
|
| 102 |
"value": "vibrant natural colors, high quality photo",
|
| 103 |
"type": "text",
|
| 104 |
"description": "Additional descriptive text to enhance the caption"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
}
|
| 106 |
]
|
| 107 |
},
|
|
@@ -127,6 +161,11 @@
|
|
| 127 |
"key": "Authorization",
|
| 128 |
"value": "Bearer {{firebase_token}}",
|
| 129 |
"type": "text"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
}
|
| 131 |
],
|
| 132 |
"body": {
|
|
@@ -161,6 +200,18 @@
|
|
| 161 |
"value": "8",
|
| 162 |
"type": "text",
|
| 163 |
"description": "Number of inference steps"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
}
|
| 165 |
]
|
| 166 |
},
|
|
|
|
| 51 |
"key": "Authorization",
|
| 52 |
"value": "Bearer {{firebase_token}}",
|
| 53 |
"type": "text"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"key": "X-Firebase-AppCheck",
|
| 57 |
+
"value": "{{firebase_app_check}}",
|
| 58 |
+
"type": "text"
|
| 59 |
}
|
| 60 |
],
|
| 61 |
"body": {
|
|
|
|
| 66 |
"type": "file",
|
| 67 |
"src": [],
|
| 68 |
"description": "Image file to colorize"
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"key": "user_id",
|
| 72 |
+
"value": "{{user_id}}",
|
| 73 |
+
"type": "text",
|
| 74 |
+
"description": "Optional user id (ObjectId or numeric) for media click logging"
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"key": "category_id",
|
| 78 |
+
"value": "{{category_id}}",
|
| 79 |
+
"type": "text",
|
| 80 |
+
"description": "Optional category id; endpoint default used when omitted"
|
| 81 |
}
|
| 82 |
]
|
| 83 |
},
|
|
|
|
| 103 |
"key": "Authorization",
|
| 104 |
"value": "Bearer {{firebase_token}}",
|
| 105 |
"type": "text"
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"key": "X-Firebase-AppCheck",
|
| 109 |
+
"value": "{{firebase_app_check}}",
|
| 110 |
+
"type": "text"
|
| 111 |
}
|
| 112 |
],
|
| 113 |
"body": {
|
|
|
|
| 124 |
"value": "vibrant natural colors, high quality photo",
|
| 125 |
"type": "text",
|
| 126 |
"description": "Additional descriptive text to enhance the caption"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"key": "user_id",
|
| 130 |
+
"value": "{{user_id}}",
|
| 131 |
+
"type": "text",
|
| 132 |
+
"description": "Optional user id (ObjectId or numeric) for media click logging"
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"key": "category_id",
|
| 136 |
+
"value": "{{category_id}}",
|
| 137 |
+
"type": "text",
|
| 138 |
+
"description": "Optional category id; endpoint default used when omitted"
|
| 139 |
}
|
| 140 |
]
|
| 141 |
},
|
|
|
|
| 161 |
"key": "Authorization",
|
| 162 |
"value": "Bearer {{firebase_token}}",
|
| 163 |
"type": "text"
|
| 164 |
+
},
|
| 165 |
+
{
|
| 166 |
+
"key": "X-Firebase-AppCheck",
|
| 167 |
+
"value": "{{firebase_app_check}}",
|
| 168 |
+
"type": "text"
|
| 169 |
}
|
| 170 |
],
|
| 171 |
"body": {
|
|
|
|
| 200 |
"value": "8",
|
| 201 |
"type": "text",
|
| 202 |
"description": "Number of inference steps"
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"key": "user_id",
|
| 206 |
+
"value": "{{user_id}}",
|
| 207 |
+
"type": "text",
|
| 208 |
+
"description": "Optional user id (ObjectId or numeric) for media click logging"
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"key": "category_id",
|
| 212 |
+
"value": "{{category_id}}",
|
| 213 |
+
"type": "text",
|
| 214 |
+
"description": "Optional category id; endpoint default used when omitted"
|
| 215 |
}
|
| 216 |
]
|
| 217 |
},
|