From 4e6d86e83819c1afa929c3810d2436feb6b21a53 Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Mon, 14 Oct 2024 13:16:32 -0500 Subject: [PATCH 1/6] change 'reset -> update' task bundling naming convention --- .../framework/controller/TaskBundleController.scala | 6 +++--- .../framework/repository/TaskBundleRepository.scala | 4 ++-- .../maproulette/framework/service/TaskBundleService.scala | 6 +++--- conf/v2_route/bundle.api | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/org/maproulette/framework/controller/TaskBundleController.scala b/app/org/maproulette/framework/controller/TaskBundleController.scala index 52221010..2838e997 100644 --- a/app/org/maproulette/framework/controller/TaskBundleController.scala +++ b/app/org/maproulette/framework/controller/TaskBundleController.scala @@ -262,17 +262,17 @@ class TaskBundleController @Inject() ( } /** - * Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle + * Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle * * @param bundleId The id of the bundle * @param taskIds The task ids the bundle will reset to */ - def resetTaskBundle( + def updateTaskBundle( id: Long, taskIds: List[Long] ): Action[AnyContent] = Action.async { implicit request => this.sessionManager.authenticatedRequest { implicit user => - this.serviceManager.taskBundle.resetTaskBundle(user, id, taskIds) + this.serviceManager.taskBundle.updateTaskBundle(user, id, taskIds) Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id))) } } diff --git a/app/org/maproulette/framework/repository/TaskBundleRepository.scala b/app/org/maproulette/framework/repository/TaskBundleRepository.scala index 1e322d7c..264c132e 100644 --- a/app/org/maproulette/framework/repository/TaskBundleRepository.scala +++ b/app/org/maproulette/framework/repository/TaskBundleRepository.scala @@ -112,12 +112,12 @@ class TaskBundleRepository @Inject() ( } /** - * Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle + * Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle * * @param bundleId The id of the bundle * @param taskIds The task ids the bundle will reset to */ - def resetTaskBundle( + def updateTaskBundle( user: User, bundleId: Long, taskIds: List[Long] diff --git a/app/org/maproulette/framework/service/TaskBundleService.scala b/app/org/maproulette/framework/service/TaskBundleService.scala index 318a6c54..0f17c2ed 100644 --- a/app/org/maproulette/framework/service/TaskBundleService.scala +++ b/app/org/maproulette/framework/service/TaskBundleService.scala @@ -90,12 +90,12 @@ class TaskBundleService @Inject() ( } /** - * Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle + * Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle * * @param bundleId The id of the bundle * @param taskIds The task ids the bundle will reset to */ - def resetTaskBundle( + def updateTaskBundle( user: User, bundleId: Long, taskIds: List[Long] @@ -108,7 +108,7 @@ class TaskBundleService @Inject() ( ) } - this.repository.resetTaskBundle(user, bundleId, taskIds) + this.repository.updateTaskBundle(user, bundleId, taskIds) this.getTaskBundle(user, bundleId) } diff --git a/conf/v2_route/bundle.api b/conf/v2_route/bundle.api index 567afd2d..e5ea5ada 100644 --- a/conf/v2_route/bundle.api +++ b/conf/v2_route/bundle.api @@ -47,8 +47,8 @@ POST /taskBundle @org.maproulette.framework.c POST /taskBundle/:id @org.maproulette.framework.controller.TaskBundleController.getTaskBundle(id:Long, lockTasks:Boolean ?= false) ### # tags: [ Bundle ] -# summary: Resets a Task Bundle -# description: Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle +# summary: Updates a Task Bundle +# description: Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle # responses: # '200': # description: Ok with empty body @@ -63,7 +63,7 @@ POST /taskBundle/:id @org.maproulette.framework. # in: query # description: The task ids the bundle will reset to ### -POST /taskBundle/:id/reset @org.maproulette.framework.controller.TaskBundleController.resetTaskBundle(id: Long, taskIds: List[Long]) +POST /taskBundle/:id/update @org.maproulette.framework.controller.TaskBundleController.updateTaskBundle(id: Long, taskIds: List[Long]) ### # tags: [ Bundle ] # summary: Deletes a Task Bundle From 459cfb6319335330b2e0a9084411f5692c99daaf Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Mon, 14 Oct 2024 22:45:05 -0500 Subject: [PATCH 2/6] fix task status issues --- .../controller/TaskBundleController.scala | 5 ++- .../repository/TaskBundleRepository.scala | 32 ++++++++++--------- .../framework/service/TaskBundleService.scala | 5 ++- conf/v2_route/bundle.api | 6 +--- .../service/TaskBundleServiceSpec.scala | 4 +-- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/app/org/maproulette/framework/controller/TaskBundleController.scala b/app/org/maproulette/framework/controller/TaskBundleController.scala index 2838e997..94c8f3c2 100644 --- a/app/org/maproulette/framework/controller/TaskBundleController.scala +++ b/app/org/maproulette/framework/controller/TaskBundleController.scala @@ -286,11 +286,10 @@ class TaskBundleController @Inject() ( */ def unbundleTasks( id: Long, - taskIds: List[Long], - preventTaskIdUnlocks: List[Long] + taskIds: List[Long] ): Action[AnyContent] = Action.async { implicit request => this.sessionManager.authenticatedRequest { implicit user => - this.serviceManager.taskBundle.unbundleTasks(user, id, taskIds, preventTaskIdUnlocks) + this.serviceManager.taskBundle.unbundleTasks(user, id, taskIds) Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id))) } } diff --git a/app/org/maproulette/framework/repository/TaskBundleRepository.scala b/app/org/maproulette/framework/repository/TaskBundleRepository.scala index 264c132e..df87e1c9 100644 --- a/app/org/maproulette/framework/repository/TaskBundleRepository.scala +++ b/app/org/maproulette/framework/repository/TaskBundleRepository.scala @@ -91,13 +91,7 @@ class TaskBundleRepository @Inject() ( val parameters = lockedTasks.map(task => Seq[NamedParameter]("taskId" -> task.id)) BatchSql(sqlInsertTaskBundles, parameters.head, parameters.tail: _*).execute() - // Lock each of the new tasks to indicate they are part of the bundle for (task <- lockedTasks) { - try { - this.lockItem(user, task) - } catch { - case e: Exception => this.logger.warn(e.getMessage) - } taskRepository.cacheManager.cache.remove(task.id) } @@ -134,7 +128,7 @@ class TaskBundleRepository @Inject() ( val tasksToRemove = currentTaskIds.filter(taskId => !taskIds.contains(taskId)) if (tasksToRemove.nonEmpty) { - this.unbundleTasks(user, bundleId, tasksToRemove, List.empty) + this.unbundleTasks(user, bundleId, tasksToRemove) } // Filter for tasks that need to be added back to the bundle. @@ -226,8 +220,7 @@ class TaskBundleRepository @Inject() ( def unbundleTasks( user: User, bundleId: Long, - taskIds: List[Long], - preventTaskIdUnlocks: List[Long] + taskIds: List[Long] ): Unit = { this.withMRConnection { implicit c => val tasks = this.retrieveTasks( @@ -265,12 +258,10 @@ class TaskBundleRepository @Inject() ( ) .executeUpdate() - if (!preventTaskIdUnlocks.contains(task.id)) { - try { - this.unlockItem(user, task) - } catch { - case e: Exception => this.logger.warn(e.getMessage) - } + try { + this.unlockItem(user, task) + } catch { + case e: Exception => this.logger.warn(e.getMessage) } taskRepository.cacheManager.cache.remove(task.id) case None => // do nothing @@ -306,8 +297,19 @@ class TaskBundleRepository @Inject() ( .on("bundleId" -> bundleId) .executeUpdate() + // Update cache for each task tasks.foreach { task => if (!task.isBundlePrimary.getOrElse(false)) { + SQL( + """UPDATE tasks + SET status = {status} + WHERE id = {taskId} + """ + ).on( + "taskId" -> task.id, + "status" -> STATUS_CREATED + ) + .executeUpdate() try { this.unlockItem(user, task) } catch { diff --git a/app/org/maproulette/framework/service/TaskBundleService.scala b/app/org/maproulette/framework/service/TaskBundleService.scala index 0f17c2ed..80b06422 100644 --- a/app/org/maproulette/framework/service/TaskBundleService.scala +++ b/app/org/maproulette/framework/service/TaskBundleService.scala @@ -120,8 +120,7 @@ class TaskBundleService @Inject() ( def unbundleTasks( user: User, bundleId: Long, - taskIds: List[Long], - preventTaskIdUnlocks: List[Long] + taskIds: List[Long] )(): TaskBundle = { val bundle = this.getTaskBundle(user, bundleId) @@ -132,7 +131,7 @@ class TaskBundleService @Inject() ( ) } - this.repository.unbundleTasks(user, bundleId, taskIds, preventTaskIdUnlocks) + this.repository.unbundleTasks(user, bundleId, taskIds) this.getTaskBundle(user, bundleId) } diff --git a/conf/v2_route/bundle.api b/conf/v2_route/bundle.api index e5ea5ada..3a6d25ff 100644 --- a/conf/v2_route/bundle.api +++ b/conf/v2_route/bundle.api @@ -104,9 +104,5 @@ DELETE /taskBundle/:id @org.maproulette.framework.c # in: query # description: The list of task ids to remove from the bundle # required: true -# - name: preventTaskIdUnlocks -# in: query -# description: The list of task ids to keep locked when removed from bundle -# required: true ### -POST /taskBundle/:id/unbundle @org.maproulette.framework.controller.TaskBundleController.unbundleTasks(id:Long, taskIds:List[Long], preventTaskIdUnlocks:List[Long]) +POST /taskBundle/:id/unbundle @org.maproulette.framework.controller.TaskBundleController.unbundleTasks(id:Long, taskIds:List[Long]) diff --git a/test/org/maproulette/framework/service/TaskBundleServiceSpec.scala b/test/org/maproulette/framework/service/TaskBundleServiceSpec.scala index f3eb038b..1f0ed8ff 100644 --- a/test/org/maproulette/framework/service/TaskBundleServiceSpec.scala +++ b/test/org/maproulette/framework/service/TaskBundleServiceSpec.scala @@ -239,7 +239,7 @@ class TaskBundleServiceSpec(implicit val application: Application) extends Frame primaryTaskId = Some(task1.id) ) - this.service.unbundleTasks(User.superUser, bundle.bundleId, List(task2.id), List(task1.id))() + this.service.unbundleTasks(User.superUser, bundle.bundleId, List(task2.id))() val response = this.service.getTaskBundle(User.superUser, bundle.bundleId) response.taskIds.length mustEqual 1 response.taskIds.head mustEqual task1.id @@ -281,7 +281,7 @@ class TaskBundleServiceSpec(implicit val application: Application) extends Frame // Random user is not allowed to delete this bundle an[IllegalAccessException] should be thrownBy - this.service.unbundleTasks(randomUser, bundle.bundleId, List(task2.id), List())() + this.service.unbundleTasks(randomUser, bundle.bundleId, List(task2.id))() } } From 46e3f18032bddea958efdce4863b983daa01a6e6 Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Thu, 21 Nov 2024 16:35:05 -0600 Subject: [PATCH 3/6] remove task lock finct --- .../repository/TaskBundleRepository.scala | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/org/maproulette/framework/repository/TaskBundleRepository.scala b/app/org/maproulette/framework/repository/TaskBundleRepository.scala index df87e1c9..3745c006 100644 --- a/app/org/maproulette/framework/repository/TaskBundleRepository.scala +++ b/app/org/maproulette/framework/repository/TaskBundleRepository.scala @@ -201,12 +201,6 @@ class TaskBundleRepository @Inject() ( } lockedTasks.foreach { task => - try { - this.lockItem(user, task) - } catch { - case e: Exception => - this.logger.warn(e.getMessage) - } taskRepository.cacheManager.cache.remove(task.id) } } @@ -258,11 +252,6 @@ class TaskBundleRepository @Inject() ( ) .executeUpdate() - try { - this.unlockItem(user, task) - } catch { - case e: Exception => this.logger.warn(e.getMessage) - } taskRepository.cacheManager.cache.remove(task.id) case None => // do nothing } @@ -310,11 +299,6 @@ class TaskBundleRepository @Inject() ( "status" -> STATUS_CREATED ) .executeUpdate() - try { - this.unlockItem(user, task) - } catch { - case e: Exception => this.logger.warn(e.getMessage) - } } taskRepository.cacheManager.cache.remove(task.id) } From a60408cc3cc11972ee14d369d1a9166dc167b4ec Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Mon, 23 Dec 2024 18:39:41 -0600 Subject: [PATCH 4/6] add bundle locking endpoints --- .../controllers/api/TaskController.scala | 78 +++++++++++++++++++ conf/v2_route/task.api | 63 +++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/app/org/maproulette/controllers/api/TaskController.scala b/app/org/maproulette/controllers/api/TaskController.scala index ad7e5cab..e3fd083e 100644 --- a/app/org/maproulette/controllers/api/TaskController.scala +++ b/app/org/maproulette/controllers/api/TaskController.scala @@ -369,6 +369,84 @@ class TaskController @Inject() ( } } + /** + * Locks a bundle of tasks based on the provided task IDs. + * + * @param taskIds The IDs of the tasks to lock + * @return + */ + def lockTaskBundleByIds(taskIds: List[Long]): Action[AnyContent] = Action.async { + implicit request => + this.sessionManager.authenticatedRequest { implicit user => + val results = taskIds.map { taskId => + this.dal.retrieveById(taskId) match { + case Some(task) => + try { + this.dal.lockItem(user, task) + Some(taskId) + } catch { + case _: LockedException => None + } + case None => None + } + }.flatten + + Ok(Json.toJson(results)) + } + } + + /** + * Unlocks a bundle of tasks based on the provided task IDs. + * + * @param taskIds The IDs of the tasks to unlock + * @return + */ + def unlockTaskBundleByIds(taskIds: List[Long]): Action[AnyContent] = Action.async { + implicit request => + this.sessionManager.authenticatedRequest { implicit user => + val results = taskIds.map { taskId => + this.dal.retrieveById(taskId) match { + case Some(task) => + try { + this.dal.unlockItem(user, task) + Some(taskId) + } catch { + case _: Exception => None + } + case None => None + } + }.flatten + + Ok(Json.toJson(results)) + } + } + + /** + * Refreshes the locks on a bundle of tasks based on the provided task IDs. + * + * @param taskIds The IDs of the tasks to refresh locks + * @return + */ + def refreshTaskLocksByIds(taskIds: List[Long]): Action[AnyContent] = Action.async { + implicit request => + this.sessionManager.authenticatedRequest { implicit user => + val results = taskIds.map { taskId => + this.dal.retrieveById(taskId) match { + case Some(task) => + try { + this.dal.refreshItemLock(user, task) + Some(taskId) + } catch { + case _: LockedException => None + } + case None => None + } + }.flatten + + Ok(Json.toJson(results)) + } + } + /** * Gets a random task(s) given the provided tags. * diff --git a/conf/v2_route/task.api b/conf/v2_route/task.api index 39115821..9d127bc1 100644 --- a/conf/v2_route/task.api +++ b/conf/v2_route/task.api @@ -916,3 +916,66 @@ GET /tasks/box/:left/:bottom/:right/:top @org.maproulette.framework.c # description: Success ### GET /taskCluster @org.maproulette.framework.controller.TaskController.getTaskClusters(points:Int ?= 100) + +### +# tags: [ Task ] +# summary: Locks a bundle of tasks +# description: Locks the specified tasks in the bundle. +# responses: +# '200': +# description: Success message +# '401': +# description: The user is not authorized to make this request +# requestBody: +# description: A JSON array of task IDs to lock. +# required: true +# content: +# application/json: +# schema: +# type: array +# items: +# type: integer +### +POST /task/bundle/lock @org.maproulette.controllers.api.TaskController.lockTaskBundleByIds(taskIds:List[Long]) + +### +# tags: [ Task ] +# summary: Unlocks a bundle of tasks +# description: Unlocks the specified tasks in the bundle. +# responses: +# '200': +# description: Success message +# '401': +# description: The user is not authorized to make this request +# requestBody: +# description: A JSON array of task IDs to unlock. +# required: true +# content: +# application/json: +# schema: +# type: array +# items: +# type: integer +### +POST /task/bundle/unlock @org.maproulette.controllers.api.TaskController.unlockTaskBundleByIds(taskIds:List[Long]) + +### +# tags: [ Task ] +# summary: Refreshes locks on a bundle of tasks +# description: Refreshes the locks on the specified tasks in the bundle. +# responses: +# '200': +# description: Success message +# '401': +# description: The user is not authorized to make this request +# requestBody: +# description: A JSON array of task IDs to refresh locks. +# required: true +# content: +# application/json: +# schema: +# type: array +# items: +# type: integer +### +POST /task/bundle/refresh @org.maproulette.controllers.api.TaskController.refreshTaskLocksByIds(taskIds:List[Long]) From 69ca85e9abd9f713d148ee0f2e04534a9e66be55 Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Sun, 29 Dec 2024 18:58:55 -0600 Subject: [PATCH 5/6] make bundling data more usefule --- .../controllers/api/TaskController.scala | 42 +++++++++++-------- conf/v2_route/task.api | 7 +--- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/app/org/maproulette/controllers/api/TaskController.scala b/app/org/maproulette/controllers/api/TaskController.scala index e3fd083e..9d28ea1b 100644 --- a/app/org/maproulette/controllers/api/TaskController.scala +++ b/app/org/maproulette/controllers/api/TaskController.scala @@ -378,20 +378,26 @@ class TaskController @Inject() ( def lockTaskBundleByIds(taskIds: List[Long]): Action[AnyContent] = Action.async { implicit request => this.sessionManager.authenticatedRequest { implicit user => - val results = taskIds.map { taskId => - this.dal.retrieveById(taskId) match { - case Some(task) => - try { - this.dal.lockItem(user, task) - Some(taskId) - } catch { - case _: LockedException => None - } - case None => None - } - }.flatten + val (lockedTasks, failedTasks) = taskIds.foldLeft((List.empty[Task], List.empty[Long])) { + case ((locked, failed), taskId) => + this.dal.retrieveById(taskId) match { + case Some(task) => + try { + this.dal.lockItem(user, task) + (task :: locked, failed) + } catch { + case _: LockedException => (locked, taskId :: failed) + } + case None => (locked, failed) + } + } - Ok(Json.toJson(results)) + if (failedTasks.nonEmpty) { + lockedTasks.foreach(task => this.dal.unlockItem(user, task)) + Ok(Json.toJson(failedTasks, false)) + } else { + Ok(Json.toJson(lockedTasks, true)) + } } } @@ -404,7 +410,7 @@ class TaskController @Inject() ( def unlockTaskBundleByIds(taskIds: List[Long]): Action[AnyContent] = Action.async { implicit request => this.sessionManager.authenticatedRequest { implicit user => - val results = taskIds.map { taskId => + val tasks = taskIds.flatMap { taskId => this.dal.retrieveById(taskId) match { case Some(task) => try { @@ -415,9 +421,9 @@ class TaskController @Inject() ( } case None => None } - }.flatten + } - Ok(Json.toJson(results)) + Ok(Json.toJson(tasks)) } } @@ -430,7 +436,7 @@ class TaskController @Inject() ( def refreshTaskLocksByIds(taskIds: List[Long]): Action[AnyContent] = Action.async { implicit request => this.sessionManager.authenticatedRequest { implicit user => - val results = taskIds.map { taskId => + val result = taskIds.map { taskId => this.dal.retrieveById(taskId) match { case Some(task) => try { @@ -443,7 +449,7 @@ class TaskController @Inject() ( } }.flatten - Ok(Json.toJson(results)) + Ok(Json.toJson(result)) } } diff --git a/conf/v2_route/task.api b/conf/v2_route/task.api index 9d127bc1..aeafbb62 100644 --- a/conf/v2_route/task.api +++ b/conf/v2_route/task.api @@ -916,14 +916,13 @@ GET /tasks/box/:left/:bottom/:right/:top @org.maproulette.framework.c # description: Success ### GET /taskCluster @org.maproulette.framework.controller.TaskController.getTaskClusters(points:Int ?= 100) - ### # tags: [ Task ] # summary: Locks a bundle of tasks -# description: Locks the specified tasks in the bundle. +# description: Attempts to lock a set of tasks. If successful, returns the tasks that were locked. If not successful, returns the tasks that were not locked. # responses: # '200': -# description: Success message +# description: List of tasks that were locked or list of tasks that were not locked. Boolean indicates if the tasks were locked. # '401': # description: The user is not authorized to make this request # requestBody: @@ -937,7 +936,6 @@ GET /taskCluster @org.maproulette.framework.c # type: integer ### POST /task/bundle/lock @org.maproulette.controllers.api.TaskController.lockTaskBundleByIds(taskIds:List[Long]) - ### # tags: [ Task ] # summary: Unlocks a bundle of tasks @@ -958,7 +956,6 @@ POST /task/bundle/lock @org.maproulette.controllers.a # type: integer ### POST /task/bundle/unlock @org.maproulette.controllers.api.TaskController.unlockTaskBundleByIds(taskIds:List[Long]) - ### # tags: [ Task ] # summary: Refreshes locks on a bundle of tasks From 5e0fa47d686eea7a0272bdc3ba6b3776ba20ff7a Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Tue, 7 Jan 2025 07:19:13 -0600 Subject: [PATCH 6/6] simplify bundle creation --- .../repository/TaskBundleRepository.scala | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/app/org/maproulette/framework/repository/TaskBundleRepository.scala b/app/org/maproulette/framework/repository/TaskBundleRepository.scala index 3745c006..63c57ae2 100644 --- a/app/org/maproulette/framework/repository/TaskBundleRepository.scala +++ b/app/org/maproulette/framework/repository/TaskBundleRepository.scala @@ -51,7 +51,8 @@ class TaskBundleRepository @Inject() ( taskIds: List[Long], verifyTasks: (List[Task]) => Unit ): TaskBundle = { - this.withMRTransaction { implicit c => + // First transaction: verify tasks and create bundle + val bundleId = this.withMRTransaction { implicit c => val lockedTasks = this.withListLocking(user, Some(TaskType())) { () => this.taskDAL.retrieveListById(-1, 0)(taskIds) } @@ -70,39 +71,26 @@ class TaskBundleRepository @Inject() ( SQL"""INSERT INTO bundles (owner_id, name) VALUES (${user.id}, ${name})""".executeInsert() rowId match { - case Some(bundleId: Long) => - // Update the task object to bind it to the bundle - SQL(s"""UPDATE tasks SET bundle_id = $bundleId - WHERE id IN ({inList})""") - .on( - "inList" -> taskIds - ) - .executeUpdate() - - primaryId match { - case Some(id) => - val sqlQuery = s"""UPDATE tasks SET is_bundle_primary = true WHERE id = $id""" - SQL(sqlQuery).executeUpdate() - case None => // Handle the case where primaryId is None - } - - val sqlInsertTaskBundles = - s"""INSERT INTO task_bundles (task_id, bundle_id) VALUES ({taskId}, $bundleId)""" - val parameters = lockedTasks.map(task => Seq[NamedParameter]("taskId" -> task.id)) - BatchSql(sqlInsertTaskBundles, parameters.head, parameters.tail: _*).execute() - - for (task <- lockedTasks) { - taskRepository.cacheManager.cache.remove(task.id) + case Some(id: Long) => + // Set primary task if specified + primaryId.foreach { pid => + SQL"""UPDATE tasks SET is_bundle_primary = true WHERE id = $pid""".executeUpdate() } - - TaskBundle(bundleId, user.id, lockedTasks.map(task => { - task.id - }), Some(lockedTasks)) - + id case None => throw new Exception("Bundle creation failed") } } + + // Second transaction: add tasks to bundle + this.bundleTasks(user, bundleId, taskIds) + + val lockedTasks = this.withListLocking(user, Some(TaskType())) { () => + this.taskDAL.retrieveListById(-1, 0)(taskIds) + } + + // Return the created bundle + TaskBundle(bundleId, user.id, taskIds, Some(lockedTasks)) } /**