api.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import type { AuthRequestHandler } from '../auth'
  2. import type { Context } from '../types'
  3. import { ObjectId } from 'mongodb'
  4. import type { SearchResult } from '../api'
  5. import type { WithId } from 'mongodb'
  6. import type { Herd, HerdCreate, HerdUpdate } from './types'
  7. import { http, query, validate as v } from '@edge/misc-utils'
  8. /** Create a herd. */
  9. export function createHerd({ model }: Context): AuthRequestHandler {
  10. interface RequestData {
  11. herd: HerdCreate<string>
  12. }
  13. interface ResponseData {
  14. herd: WithId<Herd>
  15. }
  16. const readRequestData = v.validate<RequestData>({
  17. herd: {
  18. _account: v.seq(v.str, v.exactLength(24)),
  19. name: v.seq(v.str, v.minLength(1)),
  20. },
  21. })
  22. return async function (req, res, next) {
  23. if (!req.account) return http.unauthorized(res, next)
  24. try {
  25. // Read input
  26. const input = readRequestData(req.body)
  27. // Assert ability to assign herd
  28. if (!req.account._id.equals(input.herd._account)) return http.forbidden(res, next)
  29. // Create herd
  30. const herd = await model.herd.create({ ...input.herd, _account: req.account._id })
  31. if (!herd) return http.notFound(res, next, { reason: 'unexpectedly failed to get new herd' })
  32. // Send output
  33. const output: ResponseData = { herd }
  34. res.send(output)
  35. next()
  36. } catch (err) {
  37. const name = (err as Error).name
  38. if (name === 'ValidateError') {
  39. const ve = err as v.ValidateError
  40. return http.badRequest(res, next, { param: ve.param, reason: ve.message })
  41. }
  42. return next(err)
  43. }
  44. }
  45. }
  46. /** Delete a herd. */
  47. export function deleteHerd({ model }: Context): AuthRequestHandler {
  48. interface ResponseData {
  49. herd: WithId<Herd>
  50. /** Number of tasks deleted */
  51. tasks: {
  52. deletedCount: number
  53. }
  54. }
  55. return async function (req, res, next) {
  56. if (!req.account) return http.unauthorized(res, next)
  57. try {
  58. // Assert access to herd
  59. const herd = await model.herd.collection.findOne({ _id: new ObjectId(req.params.id) })
  60. if (!herd) return http.notFound(res, next)
  61. if (!req.account._id.equals(herd._account)) return http.forbidden(res, next)
  62. // Delete herd
  63. const result = await model.herd.delete(herd._id)
  64. // Send output
  65. const output: ResponseData = {
  66. herd: result.herd as WithId<Herd>,
  67. tasks: {
  68. deletedCount: result.deletedCount,
  69. },
  70. }
  71. res.send(output)
  72. next()
  73. } catch (err) {
  74. next(err)
  75. }
  76. }
  77. }
  78. /** Get a herd. */
  79. export function getHerd({ model }: Context): AuthRequestHandler {
  80. interface ResponseData {
  81. herd: WithId<Herd>
  82. }
  83. return async function (req, res, next) {
  84. if (!req.account) return http.unauthorized(res, next)
  85. try {
  86. // Assert access to herd
  87. const herd = await model.herd.collection.findOne({ _id: new ObjectId(req.params.id) })
  88. if (!herd) return http.notFound(res, next)
  89. if (!req.account._id.equals(herd._account)) return http.forbidden(res, next)
  90. // Send output
  91. const output: ResponseData = { herd }
  92. res.send(output)
  93. next()
  94. } catch (err) {
  95. return next(err)
  96. }
  97. }
  98. }
  99. /** Search herds. */
  100. export function searchHerds({ model }: Context): AuthRequestHandler {
  101. type ResponseData = SearchResult<{
  102. herd: WithId<Herd>
  103. }>
  104. return async function (req, res, next) {
  105. if (!req.account) return http.unauthorized(res, next)
  106. // Read parameters
  107. const limit = query.integer(req.query.limit, 1, 100) || 10
  108. const page = query.integer(req.query.page, 1) || 1
  109. const search = query.str(req.query.search)
  110. const sort = query.sorts(req.query.sort, ['name'], ['name', 'ASC'])
  111. // Build filter and skip
  112. const filter: Record<string, unknown> = {
  113. _account: req.account._id,
  114. }
  115. if (search) filter.$text = { $search: search }
  116. const skip = (page - 1) * limit
  117. try {
  118. // Get total documents count for filter
  119. const totalCount = await model.herd.collection.countDocuments(filter)
  120. // Build cursor
  121. let cursor = model.herd.collection.find(filter)
  122. for (const [prop, dir] of sort) {
  123. cursor = cursor.sort(prop, dir === 'ASC' ? 1 : -1)
  124. }
  125. cursor = cursor.skip(skip).limit(limit)
  126. // Get results and send output
  127. const data = await cursor.toArray()
  128. const output: ResponseData = {
  129. results: data.map(herd => ({ herd })),
  130. metadata: { limit, page, totalCount },
  131. }
  132. res.send(output)
  133. next()
  134. } catch (err) {
  135. next(err)
  136. }
  137. }
  138. }
  139. /** Update a herd. */
  140. export function updateHerd({ model }: Context): AuthRequestHandler {
  141. interface RequestData {
  142. herd: HerdUpdate<string>
  143. }
  144. interface ResponseData {
  145. herd: WithId<Herd>
  146. }
  147. const readRequestData = v.validate<RequestData>({
  148. herd: {
  149. _account: v.seq(v.optional, v.str, v.exactLength(24)),
  150. name: v.seq(v.optional, v.str, v.minLength(1)),
  151. },
  152. })
  153. return async function (req, res, next) {
  154. if (!req.account) return http.unauthorized(res, next)
  155. try {
  156. // Assert access to herd
  157. let herd = await model.herd.collection.findOne({ _id: new ObjectId(req.params.id) })
  158. if (!herd) return http.notFound(res, next)
  159. if (!req.account._id.equals(herd._account)) return http.forbidden(res, next)
  160. // Read input
  161. const input = readRequestData(req.body)
  162. if (!input.herd._account && !input.herd.name) {
  163. return http.badRequest(res, next, { reason: 'no changes' })
  164. }
  165. // Assert ability to assign herd, if specified in update
  166. if (input.herd._account) {
  167. if (!req.account._id.equals(input.herd._account)) return http.forbidden(res, next)
  168. }
  169. // Update herd
  170. herd = await model.herd.update(herd._id, {
  171. ...input.herd,
  172. _account: input.herd._account && new ObjectId(input.herd._account) || undefined,
  173. })
  174. if (!herd) return http.notFound(res, next)
  175. // Send output
  176. const output: ResponseData = { herd }
  177. res.send(output)
  178. next()
  179. } catch (err) {
  180. const name = (err as Error).name
  181. if (name === 'ValidateError') {
  182. const ve = err as v.ValidateError
  183. return http.badRequest(res, next, { param: ve.param, reason: ve.message })
  184. }
  185. return next(err)
  186. }
  187. }
  188. }