From 907a616ccc8e90356b70e6b96c930995e25be256 Mon Sep 17 00:00:00 2001 From: prabinoid <38830224+prabinoid@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:53:12 +0545 Subject: [PATCH] fix: Project search and deletion. --- backend/api/projects/actions.py | 13 ++++++----- backend/api/teams/actions.py | 25 ++++++++++++++++++---- backend/models/dtos/message_dto.py | 19 ++-------------- backend/models/postgis/project.py | 2 ++ backend/models/postgis/team.py | 16 -------------- backend/services/project_search_service.py | 14 ++++++++---- backend/services/team_service.py | 16 ++++++++------ 7 files changed, 52 insertions(+), 53 deletions(-) diff --git a/backend/api/projects/actions.py b/backend/api/projects/actions.py index 31309a4c03..0809e4a01b 100644 --- a/backend/api/projects/actions.py +++ b/backend/api/projects/actions.py @@ -160,15 +160,18 @@ async def post( }, 400 if not ProjectAdminService.is_user_action_permitted_on_project(user.id, project_id): - return { - "Error": "User is not a manager of the project", - "SubCode": "UserPermissionError", - }, 403 + return JSONResponse( + content={ + "Error": "User is not a manager of the project", + "SubCode": "UserPermissionError", + }, + status_code=403, + ) threading.Thread( target=MessageService.send_message_to_all_contributors, args=(project_id, message_dto), ).start() - return {"Success": "Messages started"}, 200 + return JSONResponse(content={"Success": "Messages started"}, status_code=200) @router.post("/{project_id}/actions/feature/") diff --git a/backend/api/teams/actions.py b/backend/api/teams/actions.py index 2913a507c5..d83260ee7e 100644 --- a/backend/api/teams/actions.py +++ b/backend/api/teams/actions.py @@ -314,6 +314,19 @@ async def post( ) +import asyncio + + +# Function to run async code in a thread +def run_asyncio_in_thread(func, *args, **kwargs): + # Create a new event loop for the thread + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + # Create a new database connection (to be used in this thread) + db = get_db() + loop.run_until_complete(func(*args, db=db, **kwargs)) + + @router.post("/{team_id}/actions/message-members/") async def post( request: Request, @@ -321,7 +334,6 @@ async def post( user: AuthUserDTO = Depends(login_required), db: Database = Depends(get_db), team_id: int = None, - message_dto: MessageDTO = Body(...), ): """ Message all team members @@ -368,9 +380,11 @@ async def post( description: Internal Server Error """ try: + request_json = await request.json() + request_json["from_user_id"] = user.id + message_dto = MessageDTO(**request_json) # Validate if team is present team = await TeamService.get_team_by_id(team_id, db) - is_manager = await TeamService.is_user_team_manager(team_id, user.id, db) if not is_manager: raise ValueError @@ -397,10 +411,13 @@ async def post( ) try: + # Start a new thread for sending messages + # Use threading to run the async function in a separate thread # threading.Thread( - # target=TeamService.send_message_to_all_team_members, - # args=(team_id, team.name, message_dto, db), + # target=run_asyncio_in_thread, + # args=(TeamService.send_message_to_all_team_members, team_id, team.name, message_dto, user.id) # ).start() + background_tasks.add_task( TeamService.send_message_to_all_team_members, team_id, diff --git a/backend/models/dtos/message_dto.py b/backend/models/dtos/message_dto.py index f6534c032c..e4525d6a77 100644 --- a/backend/models/dtos/message_dto.py +++ b/backend/models/dtos/message_dto.py @@ -54,8 +54,8 @@ class ChatMessageDTO(BaseModel): timestamp: datetime username: str - # class Config: - # populate_by_name = True + class Config: + populate_by_name = True # json_encoders = { # datetime: lambda v: v.isoformat() + "Z" if v else None @@ -76,21 +76,6 @@ class Config: json_encoders = {datetime: lambda v: v.isoformat() + "Z" if v else None} - # def dict(self, *args, **kwargs): - # """ - # Override the dict method to exclude `user_id` and `project_id` - # from the dictionary representation. - # """ - # exclude_fields = {"user_id", "project_id"} - # # Generate the dict as usual, excluding the fields - # return super().dict(*args, **kwargs, exclude=exclude_fields) - - # def dict(self, **kwargs): - # data = super().dict(**kwargs) - # if self.timestamp: - # data["timestamp"] = self.timestamp.isoformat() + "Z" - # return data - class ProjectChatDTO(BaseModel): """DTO describing all chat messages on one project""" diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py index 156ea40898..4ec7ec7e61 100644 --- a/backend/models/postgis/project.py +++ b/backend/models/postgis/project.py @@ -825,6 +825,8 @@ async def delete(self, db: Database): "tasks", "project_info", "project_chat", + "project_partnerships_history", + "project_partnerships", ] # Start a transaction to ensure atomic deletion diff --git a/backend/models/postgis/team.py b/backend/models/postgis/team.py index dff93b9c5b..ce660c34d8 100644 --- a/backend/models/postgis/team.py +++ b/backend/models/postgis/team.py @@ -121,22 +121,6 @@ class Team(Base): # organisation = relationship(Organisation, backref="teams", lazy="joined") organisation = relationship(Organisation, backref="teams") - # async def create(self, db: Database): - # """Creates and saves the current model to the DB""" - # print(self.members) - - # team = await db.execute( - # insert(Team.__table__).values( - # organisation_id=self.organisation_id, - # name=self.name, - # logo=self.logo, - # description=self.description, - # join_method=self.join_method, - # visibility=self.visibility, - # ) - # ) - # return team if team else None - async def create(self, db: Database): """Creates and saves the current model to the DB, including members if they exist.""" diff --git a/backend/services/project_search_service.py b/backend/services/project_search_service.py index 153fcacba6..ec0eb66b98 100644 --- a/backend/services/project_search_service.py +++ b/backend/services/project_search_service.py @@ -265,15 +265,21 @@ async def _filter_projects(search_dto: ProjectSearchDTO, user, db: Database): search_text = "".join( char for char in search_dto.text_search if char not in "@|&!><\\():" ) - or_search = " | ".join([x for x in search_text.split(" ") if x]) + tsquery_search = " & ".join([x for x in search_text.split(" ") if x]) + ilike_search = f"%{search_text}%" + subquery_filters.append( - "text_searchable @@ to_tsquery('english', :text_search) OR name ILIKE :text_search" + """ + text_searchable @@ to_tsquery('english', :tsquery_search) + OR name ILIKE :text_search + """ ) - params["text_search"] = or_search + params["tsquery_search"] = tsquery_search + params["text_search"] = ilike_search filters.append( """ - p.id IN ( + p.id = ANY( SELECT project_id FROM project_info WHERE {} diff --git a/backend/services/team_service.py b/backend/services/team_service.py index cfd01ece95..b4801fa4c9 100644 --- a/backend/services/team_service.py +++ b/backend/services/team_service.py @@ -631,11 +631,15 @@ async def _get_team_members(team_id: int, db: Database): @staticmethod async def _get_active_team_members(team_id: int, db: Database): - query = """ - SELECT * FROM team_members - WHERE team_id = :team_id AND active = TRUE - """ - return await db.fetch_all(query, values={"team_id": team_id}) + try: + query = """ + SELECT * FROM team_members + WHERE team_id = :team_id AND active = TRUE + """ + return await db.fetch_all(query, values={"team_id": team_id}) + except Exception as e: + print(f"Error executing query: {str(e)}") + raise @staticmethod async def activate_team_member(team_id: int, user_id: int, db: Database): @@ -791,7 +795,6 @@ async def send_message_to_all_team_members( team_members = await TeamService._get_active_team_members(team_id, db) user = await UserService.get_user_by_id(user_id, db) sender = user.username - message_dto.message = ( "A message from {}, manager of {} team:

{}".format( MessageService.get_user_profile_link(sender), @@ -799,7 +802,6 @@ async def send_message_to_all_team_members( markdown(message_dto.message, output_format="html"), ) ) - messages = [] for team_member in team_members: if team_member.user_id != user_id: