@@ -446,9 +446,14 @@ def share_room(roomId):
446446 claims = g .token_claims
447447 room = g .current_room
448448
449- inviter_share = shares_coll .find_one ({"roomId" : str (room ["_id" ]), "userId" : claims ["sub" ]})
450- if not inviter_share or inviter_share .get ("role" ) not in ("owner" ,"admin" ,"editor" ):
451- return jsonify ({"status" :"error" ,"message" :"Forbidden: Only room owner, admin, or editor can share" }), 403
449+ is_owner = (str (room .get ("ownerId" )) == claims ["sub" ])
450+ if not is_owner :
451+ inviter_share = shares_coll .find_one ({"roomId" : str (room ["_id" ]), "userId" : claims ["sub" ]})
452+ if not inviter_share or inviter_share .get ("role" ) not in ("admin" ,"editor" ):
453+ return jsonify ({"status" :"error" ,"message" :"Forbidden: Only room owner, admin, or editor can share" }), 403
454+ inviter_role = inviter_share .get ("role" )
455+ else :
456+ inviter_role = "owner"
452457
453458 data = request .get_json (force = True ) or {}
454459 usernames = data .get ("usernames" ) or []
@@ -474,7 +479,7 @@ def share_room(roomId):
474479 if role == "owner" :
475480 return jsonify ({"status" :"error" ,"message" :"Cannot invite as owner; use transfer endpoint" }), 400
476481
477- if role == "admin" and inviter_share . get ( "role" ) != "owner" :
482+ if role == "admin" and inviter_role != "owner" :
478483 return jsonify ({"status" :"error" ,"message" :"Forbidden: Only the room owner may invite admin users" }), 403
479484
480485 results = {"invited" : [], "updated" : [], "errors" : []}
@@ -1959,26 +1964,47 @@ def get_room_members(roomId):
19591964 user = g .current_user
19601965 claims = g .token_claims
19611966 room = g .current_room
1967+
1968+ members = []
1969+
1970+ # Add the owner first
1971+ owner_id = None
1972+ try :
1973+ owner_id = room .get ("ownerId" )
1974+ owner_name = room .get ("ownerName" )
1975+ if owner_id :
1976+ members .append ({
1977+ "username" : owner_name or "Unknown" ,
1978+ "userId" : owner_id ,
1979+ "role" : "owner"
1980+ })
1981+ except Exception as e :
1982+ logger .error (f"Failed to add owner to members list: { e } " )
1983+
1984+ # Add all other members from shares_coll (excluding owner if they have a share record)
19621985 try :
19631986 cursor = shares_coll .find ({"roomId" : str (room ["_id" ])}, {"username" : 1 , "userId" : 1 , "role" : 1 })
1964- members = []
19651987 for m in cursor :
19661988 if not m : continue
1989+ # Skip if this is the owner (owners shouldn't have share records, but filter just in case)
1990+ if owner_id and m .get ("userId" ) == owner_id :
1991+ continue
19671992 members .append ({
19681993 "username" : m .get ("username" ),
19691994 "userId" : m .get ("userId" ),
19701995 "role" : m .get ("role" ) or "editor"
19711996 })
1972- except Exception :
1973- members = []
1997+ except Exception as e :
1998+ logger .error (f"Failed to fetch members from shares_coll: { e } " )
1999+
19742000 return jsonify ({"status" :"ok" ,"members" : members })
19752001
19762002@rooms_bp .route ("/rooms/<roomId>/permissions" , methods = ["PATCH" ])
19772003@require_auth
19782004@require_room_access (room_id_param = "roomId" )
19792005@validate_request_data ({
19802006 "userId" : {"validator" : validate_member_id , "required" : True },
1981- "role" : {"validator" : validate_optional_string , "required" : False }
2007+ "role" : {"validator" : validate_optional_string () , "required" : False }
19822008})
19832009def update_permissions (roomId ):
19842010 """
@@ -2002,8 +2028,8 @@ def update_permissions(roomId):
20022028 caller_role = (shares_coll .find_one ({"roomId" : str (room ["_id" ]), "$or" : [{"userId" : claims ["sub" ]}, {"username" : claims ["sub" ]}]}) or {}).get ("role" )
20032029 except Exception :
20042030 caller_role = None
2005- if caller_role not in ( "owner" , "editor" , "admin" ) :
2006- return jsonify ({"status" :"error" ,"message" :"Forbidden " }), 403
2031+ if caller_role != "owner" :
2032+ return jsonify ({"status" :"error" ,"message" :"Only the room owner can change member roles " }), 403
20072033 data = request .get_json () or {}
20082034 target_user_id = data .get ("userId" )
20092035 if not target_user_id :
@@ -2232,8 +2258,18 @@ def transfer_ownership(roomId):
22322258 if not member :
22332259 return jsonify ({"status" :"error" ,"message" :"Target user is not a member of the room" }), 400
22342260 rooms_coll .update_one ({"_id" : ObjectId (roomId )}, {"$set" : {"ownerId" : str (target_user ["_id" ]), "ownerName" : target_user ["username" ], "updatedAt" : datetime .utcnow ()}})
2235- shares_coll .update_one ({"roomId" : str (room ["_id" ]), "userId" : str (target_user ["_id" ])}, {"$set" : {"role" : "owner" }})
2236- shares_coll .update_one ({"roomId" : str (room ["_id" ]), "userId" : claims ["sub" ]}, {"$set" : {"role" : "editor" }})
2261+
2262+ # Remove the new owner from shares_coll (owners don't have share records)
2263+ shares_coll .delete_one ({"roomId" : str (room ["_id" ]), "userId" : str (target_user ["_id" ])})
2264+
2265+ # Add the old owner to shares_coll as editor (create new share record for former owner)
2266+ shares_coll .insert_one ({
2267+ "roomId" : str (room ["_id" ]),
2268+ "userId" : claims ["sub" ],
2269+ "username" : claims .get ("username" ),
2270+ "role" : "editor" ,
2271+ "createdAt" : datetime .utcnow ()
2272+ })
22372273 notifications_coll .insert_one ({
22382274 "userId" : str (target_user ["_id" ]),
22392275 "type" : "ownership_transfer" ,
@@ -2268,6 +2304,10 @@ def leave_room(roomId):
22682304 claims = g .token_claims
22692305 room = g .current_room
22702306 user_id = claims ["sub" ]
2307+
2308+ if str (room .get ("ownerId" )) == user_id :
2309+ return jsonify ({"status" :"error" ,"message" :"Owner must transfer ownership before leaving" }), 400
2310+
22712311 try :
22722312 share = shares_coll .find_one ({"roomId" : str (room ["_id" ]), "$or" : [{"userId" : user_id }, {"username" : user_id }]})
22732313 except Exception :
@@ -2277,8 +2317,6 @@ def leave_room(roomId):
22772317 logger .debug ("leave_room: user %s not a member of public room %s; treating as no-op" , user_id , str (room .get ("_id" )))
22782318 return jsonify ({"status" :"ok" ,"message" :"Not a member (noop)" , "removed" : False }), 200
22792319 return jsonify ({"status" :"error" ,"message" :"Not a member" }), 400
2280- if share .get ("role" ) == "owner" :
2281- return jsonify ({"status" :"error" ,"message" :"Owner must transfer ownership before leaving" }), 400
22822320 try :
22832321 del_q = {"roomId" : str (room ["_id" ]), "$or" : [{"userId" : user_id }, {"username" : user_id }]}
22842322 shares_coll .delete_one (del_q )
0 commit comments