@@ -3277,3 +3277,82 @@ def notification_preferences():
32773277 except Exception :
32783278 return jsonify ({"status" :"error" ,"message" :"Failed to persist preferences" }), 500
32793279 return jsonify ({"status" :"ok" ,"preferences" : clean })
3280+
3281+
3282+ @rooms_bp .route ("/rooms/<roomId>/thumbnail" , methods = ["POST" ])
3283+ @require_auth
3284+ @require_room_access (room_id_param = 'roomId' )
3285+ def upload_room_thumbnail (roomId ):
3286+ try :
3287+ data = request .get_json ()
3288+ if not data :
3289+ return jsonify ({"error" : "Request body required" }), 400
3290+
3291+ thumbnail_data = data .get ('thumbnail' )
3292+ if not thumbnail_data :
3293+ return jsonify ({"error" : "thumbnail field required" }), 400
3294+
3295+ # Strip data URL prefix if present
3296+ # Format: data:image/png;base64,iVBORw0KG...
3297+ if thumbnail_data .startswith ('data:' ):
3298+ if ',' in thumbnail_data :
3299+ thumbnail_data = thumbnail_data .split (',' , 1 )[1 ]
3300+ else :
3301+ return jsonify ({"error" : "Invalid data URL format" }), 400
3302+
3303+ # Decode base64 to binary
3304+ import base64
3305+ try :
3306+ thumbnail_bytes = base64 .b64decode (thumbnail_data )
3307+ except Exception as e :
3308+ logger .error (f"Failed to decode thumbnail base64 for room { roomId } : { e } " )
3309+ return jsonify ({"error" : "Invalid base64 encoding" }), 400
3310+
3311+ # Validate minimum size (at least 100 bytes for a valid image)
3312+ if len (thumbnail_bytes ) < 100 :
3313+ return jsonify ({"error" : "Thumbnail too small, likely invalid" }), 400
3314+
3315+ # Validate maximum size (10MB limit)
3316+ if len (thumbnail_bytes ) > 10 * 1024 * 1024 :
3317+ return jsonify ({"error" : "Thumbnail too large (max 10MB)" }), 400
3318+
3319+ # Optional: Validate it's actually a PNG/JPEG using magic bytes
3320+ # PNG magic bytes: 89 50 4E 47
3321+ # JPEG magic bytes: FF D8 FF
3322+ is_png = thumbnail_bytes [:4 ] == b'\x89 PNG'
3323+ is_jpeg = thumbnail_bytes [:3 ] == b'\xff \xd8 \xff '
3324+
3325+ if not (is_png or is_jpeg ):
3326+ logger .warning (f"Thumbnail for room { roomId } doesn't appear to be PNG or JPEG" )
3327+ # Don't reject, just log warning
3328+
3329+ # Store thumbnail in room document
3330+ updated_at = datetime .utcnow ()
3331+ result = rooms_coll .update_one (
3332+ {'_id' : ObjectId (roomId )},
3333+ {
3334+ '$set' : {
3335+ 'thumbnail' : thumbnail_bytes , # Binary data
3336+ 'thumbnailUpdatedAt' : updated_at ,
3337+ 'updatedAt' : updated_at # Also update room's main timestamp
3338+ }
3339+ }
3340+ )
3341+
3342+ if result .matched_count == 0 :
3343+ return jsonify ({"error" : "Room not found" }), 404
3344+
3345+ logger .info (f"Stored thumbnail for room { roomId } : { len (thumbnail_bytes )} bytes "
3346+ f"(format: { 'PNG' if is_png else 'JPEG' if is_jpeg else 'unknown' } )" )
3347+
3348+ return jsonify ({
3349+ "status" : "success" ,
3350+ "roomId" : roomId ,
3351+ "thumbnailSize" : len (thumbnail_bytes ),
3352+ "format" : "PNG" if is_png else "JPEG" if is_jpeg else "unknown" ,
3353+ "updatedAt" : updated_at .isoformat ()
3354+ }), 200
3355+
3356+ except Exception as e :
3357+ logger .exception (f"Failed to upload thumbnail for room { roomId } : { e } " )
3358+ return jsonify ({"error" : "Internal server error" , "details" : str (e )}), 500
0 commit comments