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,