From 0fc773f5f2c6135d31c32bc824dba0d4a784faa0 Mon Sep 17 00:00:00 2001 From: prabinoid <38830224+prabinoid@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:10:17 +0545 Subject: [PATCH] * Send message to contributors and background task. * Image upload in comment section. * Last updated utc serialization in my tasks of my contributions --- backend/api/projects/actions.py | 48 ++++++++++++------- backend/api/system/image_upload.py | 11 ++--- backend/api/users/tasks.py | 7 +-- backend/models/dtos/mapping_dto.py | 9 ++-- backend/models/postgis/task.py | 5 +- backend/services/messaging/message_service.py | 31 +++++------- backend/services/validator_service.py | 2 - 7 files changed, 61 insertions(+), 52 deletions(-) diff --git a/backend/api/projects/actions.py b/backend/api/projects/actions.py index 0809e4a01b..8171d72496 100644 --- a/backend/api/projects/actions.py +++ b/backend/api/projects/actions.py @@ -1,13 +1,11 @@ -import threading - from databases import Database -from fastapi import APIRouter, Body, Depends, Request +from fastapi import APIRouter, Body, Depends, Request, BackgroundTasks from fastapi.responses import JSONResponse from loguru import logger from shapely import GEOSException from shapely.errors import TopologicalError -from backend.db import get_db +from backend.db import get_db, db_connection from backend.models.dtos.grid_dto import GridDTO from backend.models.dtos.message_dto import MessageDTO from backend.models.dtos.user_dto import AuthUserDTO @@ -101,10 +99,10 @@ async def post( @router.post("/{project_id}/actions/message-contributors/") async def post( request: Request, + background_tasks: BackgroundTasks, project_id: int, user: AuthUserDTO = Depends(login_required), db: Database = Depends(get_db), - message_dto: MessageDTO = Body(...), ): """ Send message to all contributors of a project @@ -151,15 +149,21 @@ async def post( description: Internal Server Error """ try: - message_dto.from_user_id = user.id - except ValueError as e: + request_json = await request.json() + request_json["from_user_id"] = user.id + message_dto = MessageDTO(**request_json) + except Exception as e: logger.error(f"Error validating request: {str(e)}") - return { - "Error": "Unable to send message to mappers", - "SubCode": "InvalidData", - }, 400 - - if not ProjectAdminService.is_user_action_permitted_on_project(user.id, project_id): + return JSONResponse( + content={ + "Error": "Unable to send message to contributors", + "SubCode": "InvalidData", + }, + status_code=400, + ) + if not await ProjectAdminService.is_user_action_permitted_on_project( + user.id, project_id, db + ): return JSONResponse( content={ "Error": "User is not a manager of the project", @@ -167,11 +171,19 @@ async def post( }, status_code=403, ) - threading.Thread( - target=MessageService.send_message_to_all_contributors, - args=(project_id, message_dto), - ).start() - return JSONResponse(content={"Success": "Messages started"}, status_code=200) + try: + background_tasks.add_task( + MessageService.send_message_to_all_contributors, + project_id, + message_dto, + db_connection.database, + ) + return JSONResponse(content={"Success": "Messages started"}, status_code=200) + except Exception as e: + logger.error(f"Error starting background task: {str(e)}") + return JSONResponse( + content={"Error": "Failed to send messages"}, status_code=500 + ) @router.post("/{project_id}/actions/feature/") diff --git a/backend/api/system/image_upload.py b/backend/api/system/image_upload.py index 53f5aa166b..4415b8a929 100644 --- a/backend/api/system/image_upload.py +++ b/backend/api/system/image_upload.py @@ -1,11 +1,12 @@ -from fastapi import APIRouter, Depends, Request, Body -from fastapi.responses import JSONResponse import json + import requests +from fastapi import APIRouter, Body, Depends, Request +from fastapi.responses import JSONResponse +from backend.config import settings from backend.db import get_db from backend.models.dtos.user_dto import AuthUserDTO -from backend.config import settings from backend.services.users.authentication_service import login_required router = APIRouter( @@ -16,9 +17,7 @@ ) -# class SystemImageUploadRestAPI(Resource): -# @token_auth.login_required -@router.post("/image-upload") +@router.post("/image-upload/") async def post( request: Request, user: AuthUserDTO = Depends(login_required), diff --git a/backend/api/users/tasks.py b/backend/api/users/tasks.py index 9879ff0e6b..4f9d41ef32 100644 --- a/backend/api/users/tasks.py +++ b/backend/api/users/tasks.py @@ -1,12 +1,13 @@ -from databases import Database from datetime import datetime + +from databases import Database from fastapi import APIRouter, Depends, Request from fastapi.responses import JSONResponse -from backend.services.users.user_service import UserService from backend.db import get_db from backend.models.dtos.user_dto import AuthUserDTO from backend.services.users.authentication_service import login_required +from backend.services.users.user_service import UserService router = APIRouter( prefix="/users", @@ -115,7 +116,7 @@ async def get( sort_by=sort_by, db=db, ) - return tasks.model_dump(by_alias=True) + return tasks except ValueError: print("InvalidDateRange- Date range can not be bigger than 1 year") return JSONResponse( diff --git a/backend/models/dtos/mapping_dto.py b/backend/models/dtos/mapping_dto.py index 5b4b919ab3..ceca066abc 100644 --- a/backend/models/dtos/mapping_dto.py +++ b/backend/models/dtos/mapping_dto.py @@ -1,9 +1,11 @@ from datetime import datetime -from backend.models.postgis.statuses import TaskStatus +from typing import List, Optional + +from pydantic import BaseModel, Field, ValidationError, validator + from backend.models.dtos.mapping_issues_dto import TaskMappingIssueDTO from backend.models.dtos.task_annotation_dto import TaskAnnotationDTO -from pydantic import BaseModel, Field, ValidationError, validator -from typing import List, Optional +from backend.models.postgis.statuses import TaskStatus def is_valid_mapped_status(value): @@ -107,6 +109,7 @@ class TaskDTO(BaseModel): class Config: populate_by_name = True + json_encoders = {datetime: lambda v: v.isoformat() + "Z" if v else None} class TaskDTOs(BaseModel): diff --git a/backend/models/postgis/task.py b/backend/models/postgis/task.py index 34601c060a..83b29abe98 100644 --- a/backend/models/postgis/task.py +++ b/backend/models/postgis/task.py @@ -1626,7 +1626,10 @@ async def task_as_dto( task_dto.lock_holder = user.username if user else None task_dto.task_history = task_history task_dto.last_updated = last_updated if last_updated else None - task_dto.auto_unlock_seconds = await Task.auto_unlock_delta() + unlock_delta = await Task.auto_unlock_delta() + task_dto.auto_unlock_seconds = ( + unlock_delta.total_seconds() if unlock_delta else None + ) task_dto.comments_number = comments if isinstance(comments, int) else None return task_dto diff --git a/backend/services/messaging/message_service.py b/backend/services/messaging/message_service.py index 64f535f831..fa9414d463 100644 --- a/backend/services/messaging/message_service.py +++ b/backend/services/messaging/message_service.py @@ -129,40 +129,33 @@ async def send_message_after_validation( @staticmethod async def send_message_to_all_contributors( - project_id: int, message_dto: MessageDTO, db: Database + project_id: int, message_dto: MessageDTO, database: Database ): """Sends supplied message to all contributors on specified project. Message all contributors can take over a minute to run, so this method is expected to be called on its own thread """ - # TODO: Background task. - app = ( - create_app() - ) # Because message-all run on background thread it needs it's own app context - - with app.app_context(): - contributors = await Message.get_all_contributors(project_id, db) - project = await Project.get(project_id, db) - project_name = await ProjectInfo.get_dto_for_locale( - db, project_id, project.default_locale - ).name + async with database.connection() as conn: + contributors = await Message.get_all_contributors(project_id, conn) + project = await Project.get(project_id, conn) + project_info = await ProjectInfo.get_dto_for_locale( + conn, project_id, project.default_locale + ) message_dto.message = "A message from {} managers:

{}".format( MessageService.get_project_link( - project_id, project_name, highlight=True + project_id, project_info.name, highlight=True ), markdown(message_dto.message, output_format="html"), ) - messages = [] for contributor in contributors: - message = Message.from_dto(contributor.id, message_dto) + message = Message.from_dto(contributor, message_dto) message.message_type = MessageType.BROADCAST.value message.project_id = project_id - user = await UserService.get_user_by_id(contributor.id, db) + user = await UserService.get_user_by_id(contributor, conn) messages.append( - dict(message=message, user=user, project_name=project_name) + dict(message=message, user=user, project_name=project_info.name) ) - - await MessageService._push_messages(messages, db) + await MessageService._push_messages(messages, conn) @staticmethod async def _push_messages(messages: list, db: Database): diff --git a/backend/services/validator_service.py b/backend/services/validator_service.py index c9abbf3ba5..6aef2bf0a7 100644 --- a/backend/services/validator_service.py +++ b/backend/services/validator_service.py @@ -562,7 +562,6 @@ async def revert_user_tasks(revert_dto: RevertUserTasksDTO, db: Database): "project_id": revert_dto.project_id, "task_status": TaskStatus[revert_dto.action].value, } - if TaskStatus[revert_dto.action].value == TaskStatus.BADIMAGERY.value: query += " AND mapped_by = :user_id" values["user_id"] = revert_dto.user_id @@ -571,7 +570,6 @@ async def revert_user_tasks(revert_dto: RevertUserTasksDTO, db: Database): values["user_id"] = revert_dto.user_id tasks_to_revert = await db.fetch_all(query=query, values=values) - for task in tasks_to_revert: await MappingService.undo_mapping( revert_dto.project_id,