From 87cca3a7aaf819219c72598f71c0dc18a3d456b3 Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Fri, 26 Jul 2024 21:55:35 -0500 Subject: [PATCH] add `Retrieve Users Locked Tasks` endpoint --- .../framework/controller/UserController.scala | 10 ++++ .../framework/model/LockedTask.scala | 41 ++++++++++++++++ .../UserSavedObjectsRepository.scala | 48 +++++++++++++++---- .../framework/service/UserService.scala | 17 +++++++ conf/v2_route/user.api | 24 ++++++++++ 5 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 app/org/maproulette/framework/model/LockedTask.scala diff --git a/app/org/maproulette/framework/controller/UserController.scala b/app/org/maproulette/framework/controller/UserController.scala index bcba62dff..32a449422 100644 --- a/app/org/maproulette/framework/controller/UserController.scala +++ b/app/org/maproulette/framework/controller/UserController.scala @@ -251,6 +251,16 @@ class UserController @Inject() ( } } + def getLockedTasks( + userId: Long, + limit: Long + ): Action[AnyContent] = Action.async { implicit request => + this.sessionManager.authenticatedRequest { implicit user => + val tasks = this.serviceManager.user.getLockedTasks(userId, user, limit) + Ok(Json.toJson(tasks)) + } + } + def saveTask(userId: Long, taskId: Long): Action[AnyContent] = Action.async { implicit request => this.sessionManager.authenticatedRequest { implicit user => this.serviceManager.user.saveTask(userId, taskId, user) diff --git a/app/org/maproulette/framework/model/LockedTask.scala b/app/org/maproulette/framework/model/LockedTask.scala new file mode 100644 index 000000000..c44578303 --- /dev/null +++ b/app/org/maproulette/framework/model/LockedTask.scala @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 MapRoulette contributors (see CONTRIBUTORS.md). + * Licensed under the Apache License, Version 2.0 (see LICENSE). + */ +package org.maproulette.framework.model + +import org.joda.time.DateTime +import org.maproulette.cache.CacheObject +import org.maproulette.framework.psql.CommonField +import play.api.libs.json.{Json, Format} +import play.api.libs.json.JodaWrites._ +import play.api.libs.json.JodaReads._ + +// Define the LockedTask case class +case class LockedTask( + override val id: Long, + challengeName: Option[String], + startedAt: DateTime +) extends CacheObject[Long] { + // Implement the abstract member 'name' + override def name: String = "LockedTask" +} + +/** + * Mapping between Task and Challenge and Lock + */ +case class LockedTaskData( + id: Long, + challengeName: Option[String], + startedAt: DateTime +) + +object LockedTask extends CommonField { + // Use Json.format to automatically derive both Reads and Writes + implicit val lockedTaskFormat: Format[LockedTask] = Json.format[LockedTask] +} + +// Define implicit Formats for LockedTaskData +object LockedTaskData { + implicit val lockedTaskDataFormat: Format[LockedTaskData] = Json.format[LockedTaskData] +} diff --git a/app/org/maproulette/framework/repository/UserSavedObjectsRepository.scala b/app/org/maproulette/framework/repository/UserSavedObjectsRepository.scala index bf6830780..b4eff764e 100644 --- a/app/org/maproulette/framework/repository/UserSavedObjectsRepository.scala +++ b/app/org/maproulette/framework/repository/UserSavedObjectsRepository.scala @@ -6,18 +6,14 @@ package org.maproulette.framework.repository import java.sql.Connection +import anorm.{SQL, SqlParser} +import anorm.SqlParser._ +import org.joda.time.DateTime -import anorm.SQL import javax.inject.{Inject, Singleton} -import org.maproulette.framework.model.{Challenge, SavedChallenge, SavedTasks} -import org.maproulette.framework.psql.filter.{ - BaseParameter, - FilterParameter, - Operator, - SubQueryFilter -} +import org.maproulette.framework.model.{Challenge, LockedTaskData, SavedChallenge, SavedTasks, Task} +import org.maproulette.framework.psql.filter.{BaseParameter, FilterParameter, Operator, SubQueryFilter} import org.maproulette.framework.psql._ -import org.maproulette.framework.model.Task import org.maproulette.models.dal.{ChallengeDAL, TaskDAL} import play.api.db.Database @@ -150,6 +146,40 @@ class UserSavedObjectsRepository @Inject() ( } } + /** + * Retrieves a list of locked tasks for a specific user. + * + * @param userId The ID of the user for whom you are requesting the saved challenges. + * @param limit The maximum number of tasks to return. + * @param c An optional existing connection. + * @return A list tasks the user has locked, each item containing the task ID, its locked time, and the challenge name. + */ + def getLockedTasks( + userId: Long, + limit: Long + )(implicit c: Option[Connection] = None): List[LockedTaskData] = { + this.withMRTransaction { implicit c => + val parser = for { + id <- get[Long]("id") + challengeName <- get[Option[String]]("challenges.challenge_name") + lockedTime <- get[DateTime]("locked.locked_time") + } yield (LockedTaskData(id, challengeName, lockedTime)) + + val query = """ + SELECT t.id, l.locked_time, c.name AS challenge_name + FROM tasks t + INNER JOIN locked l ON t.id = l.item_id + LEFT JOIN challenges c ON t.parent_id = c.id + WHERE l.user_id = {userId} + LIMIT {limit} + """ + + SQL(query) + .on("userId" -> userId, "limit" -> limit) + .as(parser.*) + } + } + /** * Saves the task for the user, will validate that the task actually exists first based on the * provided id diff --git a/app/org/maproulette/framework/service/UserService.scala b/app/org/maproulette/framework/service/UserService.scala index ba02db74b..1a16cdea5 100644 --- a/app/org/maproulette/framework/service/UserService.scala +++ b/app/org/maproulette/framework/service/UserService.scala @@ -841,6 +841,23 @@ class UserService @Inject() ( this.savedObjectsRepository.getSavedTasks(userId, challengeIds, paging) } + /** + * Retrieve all the tasks that have been locked by the provided user + * + * @param userId The id of the user + * @param user The user making the actual request + * @param limit + * @return A list of Tasks that have been locked by the user + */ + def getLockedTasks( + userId: Long, + user: User, + limit: Long + ): List[LockedTaskData] = { + this.permission.hasReadAccess(UserType(), user)(userId) + this.savedObjectsRepository.getLockedTasks(userId, limit) + } + /** * Saves the task for the user, will validate that the task actually exists first based on the * provided id diff --git a/conf/v2_route/user.api b/conf/v2_route/user.api index 6fbae0554..2eb0feaf3 100644 --- a/conf/v2_route/user.api +++ b/conf/v2_route/user.api @@ -321,6 +321,30 @@ DELETE /user/:userId/unsave/:challengeId @org.maproulette.framework.c GET /user/:userId/savedTasks @org.maproulette.framework.controller.UserController.getSavedTasks(userId:Long, challengeIds:String ?= "", limit:Int ?= 10, page:Int ?= 0) ### # tags: [ User ] +# summary: Retrieves Users Locked Tasks +# description: Retrieves a list of all the tasks the user with the matching id has locked +# responses: +# '200': +# description: The retrieved Tasks +# content: +# application/json: +# schema: +# type: array +# items: +# $ref: '#/components/schemas/org.maproulette.framework.model.Task' +# '401': +# description: The user is not authorized to make this request +# parameters: +# - name: userId +# in: path +# description: The id of the user to retrieve the locked tasks for +# - name: limit +# in: query +# description: Limit the number of results returned in the response. Default value is 50. +### +GET /user/:userId/lockedTasks @org.maproulette.framework.controller.UserController.getLockedTasks(userId:Long, limit:Int ?= 50) +### +# tags: [ User ] # summary: Saves a Task for a User # description: Saves a Task to a user account # responses: