|
@@ -9,13 +9,21 @@ export type TaskModel = Awaited<ReturnType<typeof createTaskModel>>
|
|
|
async function createTaskModel(ctx: Context) {
|
|
|
const collection = ctx.db.collection<Task>('task')
|
|
|
|
|
|
- /** Create a task. */
|
|
|
+ /**
|
|
|
+ * Create a task.
|
|
|
+ * If position is omitted, the task is added to the end of its herd.
|
|
|
+ */
|
|
|
async function create(input: TaskCreate) {
|
|
|
+ let position = input.position
|
|
|
+ if (!position) {
|
|
|
+ position = 1 + await collection.countDocuments({ _herd: input._herd })
|
|
|
+ }
|
|
|
+
|
|
|
const result = await collection.insertOne({
|
|
|
_herd: input._herd,
|
|
|
_account: input._account,
|
|
|
description: input.description,
|
|
|
- position: input.position,
|
|
|
+ position,
|
|
|
})
|
|
|
|
|
|
return await collection.findOne({ _id: result.insertedId })
|
|
@@ -31,6 +39,87 @@ async function createTaskModel(ctx: Context) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Move a task.
|
|
|
+ * This function updates the position of all subsequent tasks and returns the total number of tasks affected.
|
|
|
+ */
|
|
|
+ async function move(id: ObjectId | string, position: number) {
|
|
|
+ if (ctx.config.mongo.useTransactions) return moveTx(id, position)
|
|
|
+
|
|
|
+ // Update specified task
|
|
|
+ const task = await collection.findOneAndUpdate(
|
|
|
+ { _id: new ObjectId(id) },
|
|
|
+ { $set: { position } },
|
|
|
+ { returnDocument: 'after' },
|
|
|
+ )
|
|
|
+ // Not expected to happen, but type-safe
|
|
|
+ if (!task) throw new Error('failed to get updated task')
|
|
|
+
|
|
|
+ // Get subsequent tasks
|
|
|
+ const nextTasks = collection.find({
|
|
|
+ _herd: task._herd,
|
|
|
+ _id: { $ne: new ObjectId(id) },
|
|
|
+ position: { $gte: position },
|
|
|
+
|
|
|
+ }).sort('position', 1)
|
|
|
+
|
|
|
+ // Update subsequent tasks
|
|
|
+ let affectedCount = 1
|
|
|
+ for await (const task of nextTasks) {
|
|
|
+ await collection.updateOne(
|
|
|
+ { _id: task._id },
|
|
|
+ { $set: { position: position + affectedCount } },
|
|
|
+ )
|
|
|
+ affectedCount++
|
|
|
+ }
|
|
|
+
|
|
|
+ return { task, affectedCount }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Move a task using a transaction. */
|
|
|
+ async function moveTx(id: ObjectId | string, position: number) {
|
|
|
+ const session = ctx.mongo.startSession()
|
|
|
+ try {
|
|
|
+ session.startTransaction()
|
|
|
+
|
|
|
+ // Update specified task
|
|
|
+ const task = await collection.findOneAndUpdate(
|
|
|
+ { _id: new ObjectId(id) },
|
|
|
+ { $set: { position } },
|
|
|
+ { returnDocument: 'after', session },
|
|
|
+ )
|
|
|
+ // Not expected to happen, but type-safe
|
|
|
+ if (!task) throw new Error('failed to get updated task')
|
|
|
+
|
|
|
+ // Get subsequent tasks
|
|
|
+ const nextTasks = collection.find({
|
|
|
+ _herd: task._herd,
|
|
|
+ _id: { $ne: new ObjectId(id) },
|
|
|
+ position: { $gte: position },
|
|
|
+ }, { session }).sort('position', 1)
|
|
|
+
|
|
|
+ // Update subsequent tasks
|
|
|
+ let affectedCount = 1
|
|
|
+ for await (const task of nextTasks) {
|
|
|
+ await collection.updateOne(
|
|
|
+ { _id: task._id },
|
|
|
+ { $set: { position: position + affectedCount } },
|
|
|
+ { session },
|
|
|
+ )
|
|
|
+ affectedCount++
|
|
|
+ }
|
|
|
+
|
|
|
+ // Commit and return
|
|
|
+ await session.commitTransaction()
|
|
|
+ return { task, affectedCount }
|
|
|
+ } catch (err) {
|
|
|
+ await session.abortTransaction()
|
|
|
+ throw err
|
|
|
+ } finally {
|
|
|
+ await session.endSession()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Update a task.
|
|
|
*/
|
|
@@ -55,6 +144,7 @@ async function createTaskModel(ctx: Context) {
|
|
|
collection,
|
|
|
create,
|
|
|
init,
|
|
|
+ move,
|
|
|
update,
|
|
|
}
|
|
|
}
|