22
33import json
44import logging
5+ import urllib .parse
56from dataclasses import dataclass
67from typing import Any , Dict , List , Optional , Union
78from urllib .parse import urlparse
@@ -66,6 +67,30 @@ async def _get_project_followers(request: Request, user: base_models.APIUser, pr
6667
6768 return "/ap/projects/<project_id:ulid>/followers" , ["GET" ], _get_project_followers
6869
70+ def remove_project_follower (self ) -> BlueprintFactoryResponse :
71+ """Remove a follower from a project."""
72+
73+ @authenticate (self .authenticator )
74+ async def _remove_project_follower (
75+ request : Request , user : base_models .APIUser , project_id : ULID , follower_uri : str
76+ ) -> JSONResponse :
77+ try :
78+ # URL-decode the follower_uri
79+ follower_uri = urllib .parse .unquote (follower_uri )
80+
81+ # Remove the follower
82+ await self .activitypub_service .handle_unfollow (user = user , project_id = project_id , follower_actor_uri = follower_uri )
83+
84+ # Return a 204 No Content response
85+ return JSONResponse (None , status = 204 )
86+ except errors .MissingResourceError as e :
87+ return JSONResponse (
88+ {"error" : "not_found" , "message" : str (e )},
89+ status = 404 ,
90+ )
91+
92+ return "/ap/projects/<project_id:ulid>/followers/<follower_uri:path>" , ["DELETE" ], _remove_project_follower
93+
6994 def project_inbox (self ) -> BlueprintFactoryResponse :
7095 """Receive an ActivityPub activity for a project."""
7196
@@ -90,13 +115,20 @@ async def _project_inbox(request: Request, project_id: ULID) -> HTTPResponse:
90115 status = 400 ,
91116 )
92117
93- # Handle the follow request
94- # Note: We're using an admin user here because we need to access the project regardless of permissions
95- admin_user = base_models .APIUser (id = None , is_admin = True )
96- await self .activitypub_service .handle_follow (
97- user = admin_user , project_id = project_id , follower_actor_uri = actor_uri
98- )
99- return HTTPResponse (status = 200 )
118+ try :
119+ # Handle the follow request
120+ # Note: We're using an admin user here because we need to access the project regardless of permissions
121+ admin_user = base_models .APIUser (id = None , is_admin = True )
122+ await self .activitypub_service .handle_follow (
123+ user = admin_user , project_id = project_id , follower_actor_uri = actor_uri
124+ )
125+ return HTTPResponse (status = 200 )
126+ except Exception as e :
127+ logger .exception (f"Error handling follow activity: { e } " )
128+ return JSONResponse (
129+ {"error" : "internal_error" , "message" : f"Error handling follow: { str (e )} " },
130+ status = 500 ,
131+ )
100132 elif activity_type == models .ActivityType .UNDO :
101133 # Check if the object is a Follow activity
102134 object_json = activity_json .get ("object" , {})
@@ -109,13 +141,20 @@ async def _project_inbox(request: Request, project_id: ULID) -> HTTPResponse:
109141 status = 400 ,
110142 )
111143
112- # Handle the unfollow request
113- # Note: We're using an admin user here because we need to access the project regardless of permissions
114- admin_user = base_models .APIUser (id = None , is_admin = True )
115- await self .activitypub_service .handle_unfollow (
116- user = admin_user , project_id = project_id , follower_actor_uri = actor_uri
117- )
118- return HTTPResponse (status = 200 )
144+ try :
145+ # Handle the unfollow request
146+ # Note: We're using an admin user here because we need to access the project regardless of permissions
147+ admin_user = base_models .APIUser (id = None , is_admin = True )
148+ await self .activitypub_service .handle_unfollow (
149+ user = admin_user , project_id = project_id , follower_actor_uri = actor_uri
150+ )
151+ return HTTPResponse (status = 200 )
152+ except Exception as e :
153+ logger .exception (f"Error handling unfollow activity: { e } " )
154+ return JSONResponse (
155+ {"error" : "internal_error" , "message" : f"Error handling unfollow: { str (e )} " },
156+ status = 500 ,
157+ )
119158
120159 # For other activity types, just acknowledge receipt
121160 return HTTPResponse (status = 200 )
@@ -127,7 +166,7 @@ async def _project_inbox(request: Request, project_id: ULID) -> HTTPResponse:
127166 except Exception as e :
128167 logger .exception (f"Error handling activity: { e } " )
129168 return JSONResponse (
130- {"error" : "internal_error" , "message" : "An internal error occurred" },
169+ {"error" : "internal_error" , "message" : f "An internal error occurred: { str ( e ) } " },
131170 status = 500 ,
132171 )
133172
0 commit comments