Store and publish annotations on the web, as described in the Web Annotation Discovery proposal.

web-annotation-discovery-se.../routes/handlers/ annotation.ts
195 lines
5.5 KiB

  1. // Copyright (c) 2020 Jan Kaßel
  2. // Copyright (c) 2022 Gerben
  3. //
  4. // SPDX-License-Identifier: MIT
  5. import { v4 as uuid } from 'uuid';
  6. // import validateAnnotation from 'validate-web-annotation';
  7. import db from '../db.js';
  8. import { Container, sendAnnotation, extractAnnotationIdFromUrl } from '../ldp.js';
  9. import type { Request, Response } from 'express';
  10. import { asArray } from 'web-annotation-utils';
  11. export async function createAnnotation(req: Request, res: Response) {
  12. const collectionKey = `${req.params.user}/${req.params.collection}`;
  13. try {
  14. await db.get(collectionKey);
  15. } catch (err: any) {
  16. if (err.notFound) {
  17. res.status(404).send('Not found');
  18. } else {
  19. console.error(req.method, req.path, err);
  20. res.status(500).send('Internal server error');
  21. }
  22. return;
  23. }
  24. if (!req.body) {
  25. res.status(400).send('Bad request: Missing request body');
  26. return;
  27. }
  28. const annotation = req.body;
  29. // TODO Improve validator; it appears to disagree about the date format.
  30. // if (!validateAnnotation(annotation, { optionalId: true })) {
  31. // res.status(400).send('Bad request: Invalid body schema');
  32. // return;
  33. // }
  34. const id = uuid();
  35. const annotationKey = `${collectionKey}/${id}`;
  36. if (annotation.id) {
  37. annotation.via = [...asArray(annotation.via), annotation.id];
  38. }
  39. annotation.id = id;
  40. try {
  41. await db.get(annotationKey);
  42. res.status(409).send('Conflict');
  43. } catch (err: any) {
  44. if (!err.notFound) {
  45. console.error(req.method, req.path, err);
  46. res.status(500).send('Internal server error');
  47. return;
  48. }
  49. annotation.creator ??= {
  50. type: 'Person',
  51. nickname: req.params.user,
  52. };
  53. await db.put(annotationKey, annotation);
  54. const collectionInfo = await db.get(collectionKey);
  55. const containerInfo = new Container(req, collectionKey, collectionInfo);
  56. res
  57. .status(201)
  58. .header('Location', `/${annotationKey}`)
  59. .header('Content-Location', `/${annotationKey}`);
  60. sendAnnotation(req, res, annotation, containerInfo);
  61. }
  62. }
  63. export async function getAnnotation(req: Request, res: Response) {
  64. const collectionKey = `${req.params.user}/${req.params.collection}`;
  65. try {
  66. await db.get(collectionKey);
  67. } catch (err: any) {
  68. if (err.notFound) {
  69. res.status(404).send('Not found');
  70. } else {
  71. console.error(req.method, req.path, err);
  72. res.status(500).send('Internal server error');
  73. }
  74. return;
  75. }
  76. const annotationKey = `${collectionKey}/${req.params.annotation}`;
  77. try {
  78. const annotation = await db.get(annotationKey);
  79. const collectionInfo = await db.get(collectionKey);
  80. const containerInfo = new Container(req, collectionKey, collectionInfo);
  81. sendAnnotation(req, res, annotation, containerInfo);
  82. } catch (err: any) {
  83. if (err.notFound) {
  84. res.status(404).send('Not found');
  85. } else {
  86. console.error(req.method, req.path, err);
  87. res.status(500).send('Internal server error');
  88. }
  89. return;
  90. }
  91. }
  92. export async function updateAnnotation(req: Request, res: Response) {
  93. const collectionKey = `${req.params.user}/${req.params.collection}`;
  94. try {
  95. await db.get(collectionKey);
  96. } catch (err: any) {
  97. if (err.notFound) {
  98. res.status(404).send('Not found');
  99. } else {
  100. console.error(req.method, req.path, err);
  101. res.status(500).send('Internal server error');
  102. }
  103. return;
  104. }
  105. const annotation = req.body;
  106. if (
  107. !annotation ||
  108. // TODO Improve validator; it appears to disagree about the date format.
  109. // || !validateAnnotation(annotation)
  110. !annotation.id
  111. ) {
  112. res.status(400).send('Bad request: Invalid body schema');
  113. return;
  114. }
  115. const collectionInfo = await db.get(collectionKey);
  116. const containerInfo = new Container(req, collectionKey, collectionInfo);
  117. const normalizedId = extractAnnotationIdFromUrl(
  118. annotation.id,
  119. containerInfo.url,
  120. );
  121. if (!normalizedId) {
  122. res
  123. .status(400)
  124. .send(
  125. `Bad Request: Annotation ID did not match the expected container IRI: ${containerInfo.url}`,
  126. );
  127. return;
  128. }
  129. const contractedAnnotation = {
  130. ...annotation,
  131. id: normalizedId,
  132. };
  133. const annotationKey = `${collectionKey}/${contractedAnnotation.id}`;
  134. try {
  135. await db.get(annotationKey);
  136. await db.put(annotationKey, contractedAnnotation);
  137. const collectionInfo = await db.get(collectionKey);
  138. const containerInfo = new Container(req, collectionKey, collectionInfo);
  139. sendAnnotation(req, res, contractedAnnotation, containerInfo);
  140. } catch (err: any) {
  141. if (err.notFound) {
  142. res.status(404).send('Not found');
  143. } else {
  144. console.error(req.method, req.path, err);
  145. res.status(500).send('Internal server error');
  146. }
  147. return;
  148. }
  149. }
  150. export async function deleteAnnotation(req: Request, res: Response) {
  151. const collectionKey = `${req.params.user}/${req.params.collection}`;
  152. try {
  153. await db.get(collectionKey);
  154. } catch (err: any) {
  155. if (err.notFound) {
  156. res.status(404).send('Not found');
  157. } else {
  158. console.error(req.method, req.path, err);
  159. res.status(500).send('Internal server error');
  160. }
  161. return;
  162. }
  163. const annotationKey = `${collectionKey}/${req.params.annotation}`;
  164. try {
  165. await db.get(annotationKey);
  166. await db.del(annotationKey);
  167. res.status(204).send();
  168. } catch (err: any) {
  169. if (err.notFound) {
  170. res.status(404).send('Not found');
  171. } else {
  172. console.error(req.method, req.path, err);
  173. res.status(500).send('Internal server error');
  174. }
  175. return;
  176. }
  177. }