1
0

model.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import type { ClientSession } from 'mongodb'
  2. import type { Context } from '../types'
  3. import { ObjectId } from 'mongodb'
  4. import type { Task, TaskCreate, TaskUpdate } from './types'
  5. /** Model for accessing and managing tasks. */
  6. export type TaskModel = Awaited<ReturnType<typeof createTaskModel>>
  7. /** Create a task model. */
  8. async function createTaskModel(ctx: Context) {
  9. const collection = ctx.db.collection<Task>('task')
  10. /**
  11. * Create a task.
  12. * If position is omitted, the task is added to the end of its herd.
  13. */
  14. async function create(input: TaskCreate) {
  15. let position = input.position
  16. if (!position) {
  17. position = 1 + await collection.countDocuments({ _herd: input._herd })
  18. }
  19. const result = await collection.insertOne({
  20. _herd: input._herd,
  21. _account: input._account,
  22. description: input.description,
  23. position,
  24. done: Boolean(input.done),
  25. })
  26. return await collection.findOne({ _id: result.insertedId })
  27. }
  28. /** Initialize the task collection. */
  29. async function init() {
  30. await ctx.db.createCollection('task')
  31. const exists = await collection.indexExists('task_text')
  32. if (!exists) {
  33. await collection.createIndex({ description: 'text' }, { name: 'task_text' })
  34. }
  35. }
  36. /**
  37. * Move a task.
  38. * This function updates the position of all subsequent tasks and returns the total number of tasks affected.
  39. */
  40. async function move(id: ObjectId | string, position: number, session?: ClientSession) {
  41. // Update specified task
  42. const task = await collection.findOneAndUpdate(
  43. { _id: new ObjectId(id) },
  44. { $set: { position } },
  45. { returnDocument: 'after', session },
  46. )
  47. // Not expected to happen, but type-safe
  48. if (!task) throw new Error('failed to get updated task')
  49. // Get subsequent tasks
  50. const nextTasks = collection.find({
  51. _herd: task._herd,
  52. _id: { $ne: new ObjectId(id) },
  53. position: { $gte: position },
  54. }, { session }).sort('position', 1)
  55. // Update subsequent tasks
  56. let affectedCount = 1
  57. for await (const task of nextTasks) {
  58. await collection.updateOne(
  59. { _id: task._id },
  60. { $set: { position: position + affectedCount } },
  61. { session },
  62. )
  63. affectedCount++
  64. }
  65. return { task, affectedCount }
  66. }
  67. /**
  68. * Update a task.
  69. */
  70. async function update(id: ObjectId | string, input: TaskUpdate) {
  71. const changes = <Partial<Task>>{}
  72. if (input._herd) changes._herd = input._herd
  73. if (input._account) changes._account = input._account
  74. if (input.description) changes.description = input.description
  75. if (input.position !== undefined) changes.position = input.position
  76. if (input.done !== undefined) changes.done = input.done
  77. return collection.findOneAndUpdate(
  78. { _id: new ObjectId(id) },
  79. { $set: changes },
  80. { returnDocument: 'after' },
  81. )
  82. }
  83. // Initialize on startup
  84. await init()
  85. return {
  86. collection,
  87. create,
  88. init,
  89. move,
  90. update,
  91. }
  92. }
  93. export default createTaskModel