itishalogicgo commited on
Commit
e371999
·
1 Parent(s): 603d886

Add collage-maker appname support with MongoDB integration

Browse files

- Add appname parameter to InpaintRequest and all inpaint endpoints
- Add MongoDB helper functions for collage-maker database
- Auto-fetch category_id from collage-maker when appname=collage-maker
- Store media clicks in collage-maker MongoDB when appname=collage-maker
- Store appname in api_logs collection
- Add environment variables: MONGODB_COLLAGE_MAKER, MONGODB_COLLAGE_MAKER_DB_NAME, MONGODB_COLLAGE_MAKER_ADMIN_DB_NAME
- Update all endpoints: /inpaint, /inpaint-url, /inpaint-multipart, /remove-pink

Files changed (2) hide show
  1. API_Usage_Guide.md +0 -221
  2. api/main.py +163 -17
API_Usage_Guide.md DELETED
@@ -1,221 +0,0 @@
1
- # API Usage Guide - Photo Object Removal
2
-
3
- ## Overview
4
- This guide provides step-by-step instructions for using the Photo Object Removal API to remove objects from images using AI inpainting.
5
-
6
- **Base URL:** `https://logicgoinfotechspaces-object-remover.hf.space`
7
- **Authentication:** Bearer token (optional)
8
- **Storage:** Uploaded images/masks are saved in MongoDB GridFS (database `object_remover`); IDs returned by upload endpoints are pulled from GridFS before sending to Gemini.
9
- - Processing is delegated to Google Gemini/Imagen edit API; only lightweight CPU work (mask prep, file IO) happens on this server.
10
-
11
- ## Quick Start
12
-
13
- ### 1. Health Check
14
- Verify the API is running:
15
- ```bash
16
- curl -H "Authorization: Bearer <API_TOKEN>" \
17
- https://logicgoinfotechspaces-object-remover.hf.space/health
18
- ```
19
- **Response:** `{"status":"healthy"}`
20
-
21
- ### 2. Upload Image
22
- Upload the image you want to edit:
23
- ```bash
24
- curl -H "Authorization: Bearer <API_TOKEN>" \
25
- -F image=@your_image.jpg \
26
- https://logicgoinfotechspaces-object-remover.hf.space/upload-image
27
- ```
28
- **Response:** `{"id":"9cf61445-f83b-4c97-9272-c81647f90d68","filename":"your_image.jpg"}`
29
-
30
- ### 3. Upload Mask
31
- Upload a mask showing areas to remove (white/colored areas = remove):
32
- ```bash
33
- curl -H "Authorization: Bearer <API_TOKEN>" \
34
- -F mask=@mask.png \
35
- https://logicgoinfotechspaces-object-remover.hf.space/upload-mask
36
- ```
37
- **Response:** `{"id":"d044a390-dde2-408a-b7cf-d508385e56ed","filename":"mask.png"}`
38
-
39
- ### 4. Process Image
40
- Remove objects using the uploaded image and mask:
41
- ```bash
42
- curl -H "Authorization: Bearer <API_TOKEN>" \
43
- -H "Content-Type: application/json" \
44
- -d '{"image_id":"9cf61445-f83b-4c97-9272-c81647f90d68","mask_id":"d044a390-dde2-408a-b7cf-d508385e56ed","prompt":"Describe what should be removed"}' \
45
- https://logicgoinfotechspaces-object-remover.hf.space/inpaint
46
- ```
47
- **Response:** `{"result":"output_b09568698bbd4aa591b1598c01f2f745.png"}`
48
-
49
- ### 5. View Result
50
- Open the result image in your browser:
51
- ```
52
- https://logicgoinfotechspaces-object-remover.hf.space/result/output_b09568698bbd4aa591b1598c01f2f745.png
53
- ```
54
-
55
- ## Alternative Methods
56
-
57
- ### Method 1: Get Direct URL
58
- Use `/inpaint-url` to get a shareable URL:
59
- ```bash
60
- curl -H "Authorization: Bearer <API_TOKEN>" \
61
- -H "Content-Type: application/json" \
62
- -d '{"image_id":"<image_id>","mask_id":"<mask_id>","prompt":"Describe what should be removed"}' \
63
- https://logicgoinfotechspaces-object-remover.hf.space/inpaint-url
64
- ```
65
- **Response:**
66
- ```json
67
- {
68
- "result":"output_xxx.png",
69
- "url":"https://logicgoinfotechspaces-object-remover.hf.space/download/output_xxx.png"
70
- }
71
- ```
72
-
73
- ### Method 2: One-Step Processing
74
- Upload and process in a single request:
75
- ```bash
76
- curl -H "Authorization: Bearer <API_TOKEN>" \
77
- -F image=@image.jpg \
78
- -F mask=@mask.jpg \
79
- -F prompt="Describe what should be removed" \
80
- https://logicgoinfotechspaces-object-remover.hf.space/inpaint-multipart
81
- ```
82
-
83
- ### Method 3: Download Result
84
- Download the result file:
85
- ```bash
86
- curl -L https://logicgoinfotechspaces-object-remover.hf.space/download/output_xxx.png \
87
- -o result.png
88
- ```
89
-
90
- ## Postman Setup
91
-
92
- ### 1. Upload Image
93
- - **Method:** POST
94
- - **URL:** `https://logicgoinfotechspaces-object-remover.hf.space/upload-image`
95
- - **Headers:** `Authorization: Bearer <API_TOKEN>`
96
- - **Body:** form-data
97
- - Key: `image` (Type: File)
98
- - Value: Select your image file
99
-
100
- ### 2. Upload Mask
101
- - **Method:** POST
102
- - **URL:** `https://logicgoinfotechspaces-object-remover.hf.space/upload-mask`
103
- - **Headers:** `Authorization: Bearer <API_TOKEN>`
104
- - **Body:** form-data
105
- - Key: `mask` (Type: File)
106
- - Value: Select your mask file
107
-
108
- ### 3. Process Image
109
- - **Method:** POST
110
- - **URL:** `https://logicgoinfotechspaces-object-remover.hf.space/inpaint`
111
- - **Headers:**
112
- - `Authorization: Bearer <API_TOKEN>`
113
- - `Content-Type: application/json`
114
- - **Body:** raw JSON
115
- ```json
116
- {
117
- "image_id": "9cf61445-f83b-4c97-9272-c81647f90d68",
118
- "mask_id": "d044a390-dde2-408a-b7cf-d508385e56ed",
119
- "prompt": "Describe what should be removed"
120
- }
121
- ```
122
-
123
- ## Mask Creation Guide
124
-
125
- ### Mask Formats
126
- - **RGBA PNG:** Pixels with alpha=0 are treated as areas to remove
127
- - **RGB/Grayscale:** Pixels with value > 0 are treated as areas to remove
128
-
129
- ### Creating Masks
130
- 1. **Paint Method:** Use any image editor to paint white/colored areas over objects you want to remove
131
- 2. **Transparency Method:** Create a PNG with transparent areas where you want objects removed
132
- 3. **Grayscale Method:** Create a black image and paint white areas over objects to remove
133
-
134
- ### Tips
135
- - Use high contrast (white on black) for best results
136
- - Make sure mask areas are clearly defined
137
- - Avoid thin lines or small details in the mask
138
-
139
- ## Error Handling
140
-
141
- ### Common Errors
142
-
143
- **422 Unprocessable Entity**
144
- - **Cause:** Missing required fields or invalid file format
145
- - **Solution:** Check file upload and field names
146
-
147
- **404 Not Found**
148
- - **Cause:** Invalid image_id or mask_id
149
- - **Solution:** Re-upload files to get fresh IDs
150
-
151
- **401 Unauthorized**
152
- - **Cause:** Missing or invalid API token
153
- - **Solution:** Add correct Authorization header
154
-
155
- **403 Forbidden**
156
- - **Cause:** Wrong API token
157
- - **Solution:** Use the correct token you configured
158
-
159
- ### Troubleshooting
160
-
161
- **File Upload Issues in Postman:**
162
- - Remove warning icons next to filenames
163
- - Re-select files if warning appears
164
- - Try different file formats (PNG, JPG)
165
- - Check file isn't corrupted
166
-
167
- **Slow Processing:**
168
- - Large images take longer to process
169
- - Typical processing time: 1-3 seconds
170
- - Wait for completion before checking results
171
-
172
- ## Complete Workflow Example
173
-
174
- ```bash
175
- # 1. Health check
176
- curl -H "Authorization: Bearer <API_TOKEN>" \
177
- https://logicgoinfotechspaces-object-remover.hf.space/health
178
-
179
- # 2. Upload image
180
- curl -H "Authorization: Bearer <API_TOKEN>" \
181
- -F image=@photo.jpg \
182
- https://logicgoinfotechspaces-object-remover.hf.space/upload-image
183
-
184
- # 3. Upload mask
185
- curl -H "Authorization: Bearer <API_TOKEN>" \
186
- -F mask=@mask.png \
187
- https://logicgoinfotechspaces-object-remover.hf.space/upload-mask
188
-
189
- # 4. Process (replace with actual IDs from steps 2&3)
190
- curl -H "Authorization: Bearer <API_TOKEN>" \
191
- -H "Content-Type: application/json" \
192
- -d '{"image_id":"IMAGE_ID","mask_id":"MASK_ID"}' \
193
- https://logicgoinfotechspaces-object-remover.hf.space/inpaint
194
-
195
- # 5. View result (replace with actual filename from step 4)
196
- # Open in browser: https://logicgoinfotechspaces-object-remover.hf.space/result/OUTPUT_FILENAME
197
- ```
198
-
199
- ## API Endpoints Summary
200
-
201
- | Method | Endpoint | Description | Auth Required |
202
- |--------|----------|-------------|---------------|
203
- | GET | `/health` | Health check | No |
204
- | GET | `/` | API information | No |
205
- | POST | `/upload-image` | Upload image file | Optional |
206
- | POST | `/upload-mask` | Upload mask file | Optional |
207
- | POST | `/inpaint` | Process with IDs | Optional |
208
- | POST | `/inpaint-url` | Process with URL response | Optional |
209
- | POST | `/inpaint-multipart` | One-step processing | Optional |
210
- | GET | `/download/{filename}` | Download result | No |
211
- | GET | `/result/{filename}` | View result in browser | No |
212
- | GET | `/logs` | View processing logs | Optional |
213
- | GET | `/docs` | API documentation | No |
214
-
215
- ## Support
216
-
217
- For issues or questions:
218
- 1. Check the API documentation at `/docs`
219
- 2. Verify your request format matches examples
220
- 3. Ensure files are properly uploaded
221
- 4. Check authentication token is correct
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api/main.py CHANGED
@@ -98,6 +98,93 @@ ADMIN_MONGO_URI = os.environ.get("MONGODB_ADMIN")
98
  DEFAULT_CATEGORY_ID = "69368f722e46bd68ae188984"
99
  admin_media_clicks = None
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  def _init_admin_mongo() -> None:
103
  global admin_media_clicks
@@ -320,6 +407,7 @@ class InpaintRequest(BaseModel):
320
  prompt: Optional[str] = None # Optional: describe what to remove
321
  user_id: Optional[str] = None
322
  category_id: Optional[str] = None
 
323
 
324
 
325
  class SimpleRemoveRequest(BaseModel):
@@ -349,10 +437,23 @@ def _coerce_category_id(category_id: Optional[str]) -> ObjectId:
349
  return _coerce_object_id(raw_str)
350
 
351
 
352
- def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
353
- """Log to admin media_clicks collection only if user_id is provided."""
354
- if admin_media_clicks is None:
355
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  # Only log if user_id is provided (not None/empty)
357
  if not user_id or not user_id.strip():
358
  return
@@ -362,14 +463,14 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
362
  now = datetime.utcnow()
363
  today = now.date()
364
 
365
- doc = admin_media_clicks.find_one({"userId": user_obj})
366
  if doc:
367
  existing_daily = doc.get("ai_edit_daily_count")
368
  updated_daily = _build_ai_edit_daily_count(existing_daily, today)
369
  categories = doc.get("categories") or []
370
  if any(cat.get("categoryId") == category_obj for cat in categories):
371
  # Category exists: increment click_count and ai_edit_complete, update dates
372
- admin_media_clicks.update_one(
373
  {"_id": doc["_id"], "categories.categoryId": category_obj},
374
  {
375
  "$inc": {
@@ -386,7 +487,7 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
386
  )
387
  else:
388
  # New category to existing document: push category, increment ai_edit_complete
389
- admin_media_clicks.update_one(
390
  {"_id": doc["_id"]},
391
  {
392
  "$push": {
@@ -407,7 +508,7 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
407
  else:
408
  # New user: create document with default ai_edit_complete=0, then increment to 1
409
  daily_for_new = _build_ai_edit_daily_count(None, today)
410
- admin_media_clicks.update_one(
411
  {"userId": user_obj},
412
  {
413
  "$setOnInsert": {
@@ -432,15 +533,17 @@ def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
432
  )
433
  except Exception as err:
434
  err_str = str(err)
 
435
  if "Unauthorized" in err_str or "not authorized" in err_str.lower():
436
  log.warning(
437
- "Admin media click logging failed (permissions): user lacks read/write on db=%s collection=%s. "
438
  "Check MongoDB user permissions.",
439
- admin_media_clicks.database.name,
440
- admin_media_clicks.name,
 
441
  )
442
  else:
443
- log.warning("Admin media click logging failed: %s", err)
444
 
445
 
446
  @app.get("/")
@@ -612,6 +715,13 @@ def inpaint(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth
612
  compressed_url = None
613
 
614
  try:
 
 
 
 
 
 
 
615
  img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
616
  mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask")
617
  mask_rgba = _load_rgba_mask_from_image(mask_img)
@@ -643,7 +753,7 @@ def inpaint(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth
643
  log.warning("Failed to create compressed image: %s", compress_err)
644
  compressed_url = None
645
 
646
- log_media_click(req.user_id, req.category_id)
647
  response = {"result": output_name}
648
  if compressed_url:
649
  response["Compressed_Image_URL"] = compressed_url
@@ -668,6 +778,10 @@ def inpaint(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth
668
  "response_time_ms": response_time_ms
669
  }
670
 
 
 
 
 
671
  if error_msg:
672
  log_doc["error"] = error_msg
673
 
@@ -729,6 +843,13 @@ def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_
729
  result_name = None
730
 
731
  try:
 
 
 
 
 
 
 
732
  img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
733
  mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask") # may be RGB/gray/RGBA
734
  mask_rgba = _load_rgba_mask_from_image(mask_img)
@@ -748,7 +869,7 @@ def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_
748
 
749
  url = str(request.url_for("download_file", filename=result_name))
750
  logs.append({"result": result_name, "url": url, "timestamp": datetime.utcnow().isoformat()})
751
- log_media_click(req.user_id, req.category_id)
752
  return {"result": result_name, "url": url}
753
  except Exception as e:
754
  status = "fail"
@@ -767,6 +888,9 @@ def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_
767
  "ts": int(time.time()),
768
  "response_time_ms": response_time_ms,
769
  }
 
 
 
770
  if error_msg:
771
  log_doc["error"] = error_msg
772
  if mongo_logs is not None:
@@ -802,6 +926,7 @@ def inpaint_multipart(
802
  prompt: Optional[str] = Form(None),
803
  user_id: Optional[str] = Form(None),
804
  category_id: Optional[str] = Form(None),
 
805
  _: None = Depends(bearer_auth),
806
  ) -> Dict[str, str]:
807
  start_time = time.time()
@@ -810,6 +935,13 @@ def inpaint_multipart(
810
  result_name = None
811
 
812
  try:
 
 
 
 
 
 
 
813
  # Load in-memory
814
  img = Image.open(image.file).convert("RGBA")
815
  m = Image.open(mask.file).convert("RGBA")
@@ -835,7 +967,7 @@ def inpaint_multipart(
835
  resp: Dict[str, str] = {"result": result_name}
836
  if url:
837
  resp["url"] = url
838
- log_media_click(user_id, category_id)
839
  return resp
840
 
841
  if mask_is_painted:
@@ -936,7 +1068,7 @@ def inpaint_multipart(
936
  resp: Dict[str, str] = {"result": result_name}
937
  if url:
938
  resp["url"] = url
939
- log_media_click(user_id, category_id)
940
  return resp
941
  except Exception as e:
942
  status = "fail"
@@ -954,6 +1086,9 @@ def inpaint_multipart(
954
  "ts": int(time.time()),
955
  "response_time_ms": response_time_ms,
956
  }
 
 
 
957
  if error_msg:
958
  log_doc["error"] = error_msg
959
  if mongo_logs is not None:
@@ -984,6 +1119,7 @@ def remove_pink_segments(
984
  request: Request = None,
985
  user_id: Optional[str] = Form(None),
986
  category_id: Optional[str] = Form(None),
 
987
  _: None = Depends(bearer_auth),
988
  ) -> Dict[str, str]:
989
  """
@@ -998,6 +1134,13 @@ def remove_pink_segments(
998
  result_name = None
999
 
1000
  try:
 
 
 
 
 
 
 
1001
  log.info(f"Simple remove-pink: processing image {image.filename}")
1002
 
1003
  # Load the image (with pink paint on it)
@@ -1094,7 +1237,7 @@ def remove_pink_segments(
1094
  resp: Dict[str, str] = {"result": result_name, "pink_segments_detected": str(nonzero)}
1095
  if url:
1096
  resp["url"] = url
1097
- log_media_click(user_id, category_id)
1098
  return resp
1099
  except Exception as e:
1100
  status = "fail"
@@ -1112,6 +1255,9 @@ def remove_pink_segments(
1112
  "ts": int(time.time()),
1113
  "response_time_ms": response_time_ms,
1114
  }
 
 
 
1115
  if error_msg:
1116
  log_doc["error"] = error_msg
1117
  if mongo_logs is not None:
 
98
  DEFAULT_CATEGORY_ID = "69368f722e46bd68ae188984"
99
  admin_media_clicks = None
100
 
101
+ # Collage-maker MongoDB configuration
102
+ COLLAGE_MAKER_MONGO_URI = os.environ.get("MONGODB_COLLAGE_MAKER")
103
+ COLLAGE_MAKER_DB_NAME = os.environ.get("MONGODB_COLLAGE_MAKER_DB_NAME", "collage-maker")
104
+ COLLAGE_MAKER_ADMIN_DB_NAME = os.environ.get("MONGODB_COLLAGE_MAKER_ADMIN_DB_NAME", "adminPanel")
105
+ collage_maker_client = None
106
+ collage_maker_db = None
107
+ collage_maker_admin_db = None
108
+ collage_maker_media_clicks = None
109
+ collage_maker_categories = None
110
+
111
+
112
+ def get_collage_maker_client() -> Optional[MongoClient]:
113
+ """Get collage-maker MongoDB client."""
114
+ global collage_maker_client
115
+ if collage_maker_client is None and COLLAGE_MAKER_MONGO_URI:
116
+ try:
117
+ collage_maker_client = MongoClient(COLLAGE_MAKER_MONGO_URI)
118
+ log.info("Collage-maker MongoDB client initialized")
119
+ except Exception as err:
120
+ log.error("Failed to initialize collage-maker MongoDB client: %s", err)
121
+ collage_maker_client = None
122
+ return collage_maker_client
123
+
124
+
125
+ def get_collage_maker_database() -> Optional[Any]:
126
+ """Get collage-maker database instance."""
127
+ global collage_maker_db
128
+ client = get_collage_maker_client()
129
+ if client is None:
130
+ return None
131
+ if collage_maker_db is None:
132
+ try:
133
+ collage_maker_db = client[COLLAGE_MAKER_DB_NAME]
134
+ log.info("Collage-maker database initialized: %s", COLLAGE_MAKER_DB_NAME)
135
+ except Exception as err:
136
+ log.error("Failed to get collage-maker database: %s", err)
137
+ collage_maker_db = None
138
+ return collage_maker_db
139
+
140
+
141
+ def _init_collage_maker_mongo() -> None:
142
+ """Initialize collage-maker MongoDB connections."""
143
+ global collage_maker_admin_db, collage_maker_media_clicks, collage_maker_categories
144
+ client = get_collage_maker_client()
145
+ if client is None:
146
+ log.info("Collage-maker Mongo URI not provided; collage-maker features disabled")
147
+ return
148
+ try:
149
+ collage_maker_admin_db = client[COLLAGE_MAKER_ADMIN_DB_NAME]
150
+ collage_maker_media_clicks = collage_maker_admin_db["media_clicks"]
151
+ collage_maker_categories = collage_maker_admin_db["categories"]
152
+ log.info(
153
+ "Collage-maker admin initialized: db=%s, media_clicks=%s, categories=%s",
154
+ COLLAGE_MAKER_ADMIN_DB_NAME,
155
+ collage_maker_media_clicks.name,
156
+ collage_maker_categories.name,
157
+ )
158
+ except Exception as err:
159
+ log.error("Failed to init collage-maker admin Mongo: %s", err)
160
+ collage_maker_admin_db = None
161
+ collage_maker_media_clicks = None
162
+ collage_maker_categories = None
163
+
164
+
165
+ _init_collage_maker_mongo()
166
+
167
+
168
+ def get_category_id_from_collage_maker() -> Optional[str]:
169
+ """Query category ID from collage-maker categories collection."""
170
+ if collage_maker_categories is None:
171
+ log.warning("Collage-maker categories collection not initialized")
172
+ return None
173
+ try:
174
+ # Query the categories collection - you may need to adjust the query based on your schema
175
+ # This assumes there's a default category or we get the first one
176
+ category_doc = collage_maker_categories.find_one()
177
+ if category_doc:
178
+ category_id = str(category_doc.get("_id", ""))
179
+ log.info("Found category ID from collage-maker: %s", category_id)
180
+ return category_id
181
+ else:
182
+ log.warning("No categories found in collage-maker collection")
183
+ return None
184
+ except Exception as err:
185
+ log.error("Failed to query collage-maker categories: %s", err)
186
+ return None
187
+
188
 
189
  def _init_admin_mongo() -> None:
190
  global admin_media_clicks
 
407
  prompt: Optional[str] = None # Optional: describe what to remove
408
  user_id: Optional[str] = None
409
  category_id: Optional[str] = None
410
+ appname: Optional[str] = None # Optional: app name (e.g., "collage-maker")
411
 
412
 
413
  class SimpleRemoveRequest(BaseModel):
 
437
  return _coerce_object_id(raw_str)
438
 
439
 
440
+ def log_media_click(user_id: Optional[str], category_id: Optional[str], appname: Optional[str] = None) -> None:
441
+ """Log to admin media_clicks collection only if user_id is provided.
442
+
443
+ If appname='collage-maker', logs to collage-maker MongoDB instead of regular admin MongoDB.
444
+ """
445
+ # Determine which media_clicks collection to use
446
+ target_media_clicks = None
447
+ if appname == "collage-maker":
448
+ target_media_clicks = collage_maker_media_clicks
449
+ if target_media_clicks is None:
450
+ log.warning("Collage-maker media_clicks not initialized, skipping log")
451
+ return
452
+ else:
453
+ target_media_clicks = admin_media_clicks
454
+ if target_media_clicks is None:
455
+ return
456
+
457
  # Only log if user_id is provided (not None/empty)
458
  if not user_id or not user_id.strip():
459
  return
 
463
  now = datetime.utcnow()
464
  today = now.date()
465
 
466
+ doc = target_media_clicks.find_one({"userId": user_obj})
467
  if doc:
468
  existing_daily = doc.get("ai_edit_daily_count")
469
  updated_daily = _build_ai_edit_daily_count(existing_daily, today)
470
  categories = doc.get("categories") or []
471
  if any(cat.get("categoryId") == category_obj for cat in categories):
472
  # Category exists: increment click_count and ai_edit_complete, update dates
473
+ target_media_clicks.update_one(
474
  {"_id": doc["_id"], "categories.categoryId": category_obj},
475
  {
476
  "$inc": {
 
487
  )
488
  else:
489
  # New category to existing document: push category, increment ai_edit_complete
490
+ target_media_clicks.update_one(
491
  {"_id": doc["_id"]},
492
  {
493
  "$push": {
 
508
  else:
509
  # New user: create document with default ai_edit_complete=0, then increment to 1
510
  daily_for_new = _build_ai_edit_daily_count(None, today)
511
+ target_media_clicks.update_one(
512
  {"userId": user_obj},
513
  {
514
  "$setOnInsert": {
 
533
  )
534
  except Exception as err:
535
  err_str = str(err)
536
+ target_name = "collage-maker" if appname == "collage-maker" else "admin"
537
  if "Unauthorized" in err_str or "not authorized" in err_str.lower():
538
  log.warning(
539
+ "%s media click logging failed (permissions): user lacks read/write on db=%s collection=%s. "
540
  "Check MongoDB user permissions.",
541
+ target_name,
542
+ target_media_clicks.database.name,
543
+ target_media_clicks.name,
544
  )
545
  else:
546
+ log.warning("%s media click logging failed: %s", target_name, err)
547
 
548
 
549
  @app.get("/")
 
715
  compressed_url = None
716
 
717
  try:
718
+ # Handle appname="collage-maker": get category_id from collage-maker if not provided
719
+ category_id = req.category_id
720
+ if req.appname == "collage-maker" and not category_id:
721
+ category_id = get_category_id_from_collage_maker()
722
+ if category_id:
723
+ log.info("Using category_id from collage-maker: %s", category_id)
724
+
725
  img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
726
  mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask")
727
  mask_rgba = _load_rgba_mask_from_image(mask_img)
 
753
  log.warning("Failed to create compressed image: %s", compress_err)
754
  compressed_url = None
755
 
756
+ log_media_click(req.user_id, category_id, req.appname)
757
  response = {"result": output_name}
758
  if compressed_url:
759
  response["Compressed_Image_URL"] = compressed_url
 
778
  "response_time_ms": response_time_ms
779
  }
780
 
781
+ # Store appname in api_logs if provided
782
+ if req.appname:
783
+ log_doc["appname"] = req.appname
784
+
785
  if error_msg:
786
  log_doc["error"] = error_msg
787
 
 
843
  result_name = None
844
 
845
  try:
846
+ # Handle appname="collage-maker": get category_id from collage-maker if not provided
847
+ category_id = req.category_id
848
+ if req.appname == "collage-maker" and not category_id:
849
+ category_id = get_category_id_from_collage_maker()
850
+ if category_id:
851
+ log.info("Using category_id from collage-maker: %s", category_id)
852
+
853
  img_rgba = _load_rgba_image_from_gridfs(req.image_id, "image")
854
  mask_img = _load_rgba_image_from_gridfs(req.mask_id, "mask") # may be RGB/gray/RGBA
855
  mask_rgba = _load_rgba_mask_from_image(mask_img)
 
869
 
870
  url = str(request.url_for("download_file", filename=result_name))
871
  logs.append({"result": result_name, "url": url, "timestamp": datetime.utcnow().isoformat()})
872
+ log_media_click(req.user_id, category_id, req.appname)
873
  return {"result": result_name, "url": url}
874
  except Exception as e:
875
  status = "fail"
 
888
  "ts": int(time.time()),
889
  "response_time_ms": response_time_ms,
890
  }
891
+ # Store appname in api_logs if provided
892
+ if req.appname:
893
+ log_doc["appname"] = req.appname
894
  if error_msg:
895
  log_doc["error"] = error_msg
896
  if mongo_logs is not None:
 
926
  prompt: Optional[str] = Form(None),
927
  user_id: Optional[str] = Form(None),
928
  category_id: Optional[str] = Form(None),
929
+ appname: Optional[str] = Form(None),
930
  _: None = Depends(bearer_auth),
931
  ) -> Dict[str, str]:
932
  start_time = time.time()
 
935
  result_name = None
936
 
937
  try:
938
+ # Handle appname="collage-maker": get category_id from collage-maker if not provided
939
+ final_category_id = category_id
940
+ if appname == "collage-maker" and not final_category_id:
941
+ final_category_id = get_category_id_from_collage_maker()
942
+ if final_category_id:
943
+ log.info("Using category_id from collage-maker: %s", final_category_id)
944
+
945
  # Load in-memory
946
  img = Image.open(image.file).convert("RGBA")
947
  m = Image.open(mask.file).convert("RGBA")
 
967
  resp: Dict[str, str] = {"result": result_name}
968
  if url:
969
  resp["url"] = url
970
+ log_media_click(user_id, final_category_id, appname)
971
  return resp
972
 
973
  if mask_is_painted:
 
1068
  resp: Dict[str, str] = {"result": result_name}
1069
  if url:
1070
  resp["url"] = url
1071
+ log_media_click(user_id, final_category_id, appname)
1072
  return resp
1073
  except Exception as e:
1074
  status = "fail"
 
1086
  "ts": int(time.time()),
1087
  "response_time_ms": response_time_ms,
1088
  }
1089
+ # Store appname in api_logs if provided
1090
+ if appname:
1091
+ log_doc["appname"] = appname
1092
  if error_msg:
1093
  log_doc["error"] = error_msg
1094
  if mongo_logs is not None:
 
1119
  request: Request = None,
1120
  user_id: Optional[str] = Form(None),
1121
  category_id: Optional[str] = Form(None),
1122
+ appname: Optional[str] = Form(None),
1123
  _: None = Depends(bearer_auth),
1124
  ) -> Dict[str, str]:
1125
  """
 
1134
  result_name = None
1135
 
1136
  try:
1137
+ # Handle appname="collage-maker": get category_id from collage-maker if not provided
1138
+ final_category_id = category_id
1139
+ if appname == "collage-maker" and not final_category_id:
1140
+ final_category_id = get_category_id_from_collage_maker()
1141
+ if final_category_id:
1142
+ log.info("Using category_id from collage-maker: %s", final_category_id)
1143
+
1144
  log.info(f"Simple remove-pink: processing image {image.filename}")
1145
 
1146
  # Load the image (with pink paint on it)
 
1237
  resp: Dict[str, str] = {"result": result_name, "pink_segments_detected": str(nonzero)}
1238
  if url:
1239
  resp["url"] = url
1240
+ log_media_click(user_id, final_category_id, appname)
1241
  return resp
1242
  except Exception as e:
1243
  status = "fail"
 
1255
  "ts": int(time.time()),
1256
  "response_time_ms": response_time_ms,
1257
  }
1258
+ # Store appname in api_logs if provided
1259
+ if appname:
1260
+ log_doc["appname"] = appname
1261
  if error_msg:
1262
  log_doc["error"] = error_msg
1263
  if mongo_logs is not None: