@paulhectork/aiiinotate 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +206 -0
  3. package/cli/export.js +118 -0
  4. package/cli/import.js +111 -0
  5. package/cli/index.js +45 -0
  6. package/cli/migrate.js +131 -0
  7. package/cli/serve.js +29 -0
  8. package/cli/utils/fastifyClient.js +86 -0
  9. package/cli/utils/io.js +225 -0
  10. package/cli/utils/mongoClient.js +21 -0
  11. package/cli/utils/progressbar.js +86 -0
  12. package/cli/xywhToInt.js +99 -0
  13. package/config/.env.template +48 -0
  14. package/docker/Dockerfile +39 -0
  15. package/docker/README.md +5 -0
  16. package/docker/docker-compose.yaml +49 -0
  17. package/docker/docker.sh +43 -0
  18. package/docker/docker_aiiinotate_import.sh +62 -0
  19. package/docs/api.md +381 -0
  20. package/docs/cli.md +132 -0
  21. package/docs/dev_documentation/dev_architecture.md +88 -0
  22. package/docs/dev_documentation/dev_db.md +58 -0
  23. package/docs/dev_documentation/dev_iiif_compatibility.md +43 -0
  24. package/docs/dev_documentation/dev_notes_quirks_and_troubleshooting.md +143 -0
  25. package/docs/docker.md +98 -0
  26. package/docs/includes/report_benchmark_aiiinotate_2026-05-28-02:50:48_7steps.png +0 -0
  27. package/docs/scalability.md +34 -0
  28. package/docs/specifications/0_w3c_open_annotations.md +332 -0
  29. package/docs/specifications/1_w3c_web_annotations.md +577 -0
  30. package/docs/specifications/2_iiif_apis.md +428 -0
  31. package/docs/specifications/3_iiif_annotations.md +103 -0
  32. package/docs/specifications/4_search_api.md +135 -0
  33. package/docs/specifications/5_sas.md +119 -0
  34. package/docs/specifications/6_mirador.md +119 -0
  35. package/docs/specifications/7_aikon.md +137 -0
  36. package/docs/specifications/include/presentation_2.0.webp +0 -0
  37. package/docs/specifications/include/presentation_2.0_white.png +0 -0
  38. package/docs/specifications/include/presentation_3.0.png +0 -0
  39. package/docs/specifications/include/presentation_3.0_resize.png +0 -0
  40. package/eslint.config.js +30 -0
  41. package/migrations/baseConfig.js +57 -0
  42. package/migrations/manageIndex.js +55 -0
  43. package/migrations/migrate-mongo-config-main.js +8 -0
  44. package/migrations/migrate-mongo-config-test.js +8 -0
  45. package/migrations/migrationScripts/20250825185706-collections.js +48 -0
  46. package/migrations/migrationScripts/20250826194832-annotations2-schema.js +42 -0
  47. package/migrations/migrationScripts/20250904080710-annotations2-indexes.js +69 -0
  48. package/migrations/migrationScripts/20251002141951-manifests2-schema.js +43 -0
  49. package/migrations/migrationScripts/20251006212110-manifests2-indexes.js +35 -0
  50. package/migrations/migrationTemplate.js +25 -0
  51. package/package.json +82 -0
  52. package/scripts/get_version.py +12 -0
  53. package/scripts/run.sh +36 -0
  54. package/scripts/setup_mongodb.sh +70 -0
  55. package/scripts/setup_node.sh +15 -0
  56. package/scripts/update_version.py +30 -0
  57. package/scripts/utils.sh +65 -0
  58. package/src/app.js +116 -0
  59. package/src/constants.js +73 -0
  60. package/src/data/annotations/annotations2.js +681 -0
  61. package/src/data/annotations/annotations3.js +28 -0
  62. package/src/data/annotations/routes.js +335 -0
  63. package/src/data/annotations/routes.test.js +271 -0
  64. package/src/data/collectionAbstract.js +283 -0
  65. package/src/data/index.js +29 -0
  66. package/src/data/manifests/manifests2.js +378 -0
  67. package/src/data/manifests/manifests2.test.js +53 -0
  68. package/src/data/manifests/manifests3.js +23 -0
  69. package/src/data/manifests/routes.js +122 -0
  70. package/src/data/manifests/routes.test.js +70 -0
  71. package/src/data/routes.js +181 -0
  72. package/src/data/routes.test.js +166 -0
  73. package/src/db/index.js +50 -0
  74. package/src/fixtures/annotations.js +41 -0
  75. package/src/fixtures/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
  76. package/src/fixtures/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
  77. package/src/fixtures/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
  78. package/src/fixtures/data/annotations2Invalid.jsonld +39 -0
  79. package/src/fixtures/data/annotations2SvgValid.jsonld +81 -0
  80. package/src/fixtures/data/annotations2Valid.jsonld +39 -0
  81. package/src/fixtures/data/bnf_invalid_manifest.json +2806 -0
  82. package/src/fixtures/data/bnf_valid_manifest.json +2817 -0
  83. package/src/fixtures/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
  84. package/src/fixtures/generate.js +181 -0
  85. package/src/fixtures/index.js +69 -0
  86. package/src/fixtures/manifests.js +14 -0
  87. package/src/fixtures/utils.js +37 -0
  88. package/src/schemas/index.js +100 -0
  89. package/src/schemas/schemasBase.js +19 -0
  90. package/src/schemas/schemasPresentation2.js +410 -0
  91. package/src/schemas/schemasPresentation3.js +33 -0
  92. package/src/schemas/schemasResolver.js +71 -0
  93. package/src/schemas/schemasRoutes.js +318 -0
  94. package/src/server.js +25 -0
  95. package/src/types.js +97 -0
  96. package/src/utils/iiif2Utils.js +332 -0
  97. package/src/utils/iiif2Utils.test.js +146 -0
  98. package/src/utils/iiif3Utils.js +0 -0
  99. package/src/utils/iiifUtils.js +18 -0
  100. package/src/utils/logger.js +119 -0
  101. package/src/utils/routeUtils.js +137 -0
  102. package/src/utils/svg.js +417 -0
  103. package/src/utils/testUtils.js +289 -0
  104. package/src/utils/utils.js +403 -0
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e;
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6
+
7
+ # docker specific config
8
+ ENV_DOCKER="$SCRIPT_DIR/.env"
9
+
10
+ if [ ! -f "$ENV_DOCKER" ];
11
+ then echo ".env file not found at '$ENV_DOCKER'. exiting..."; exit 1
12
+ fi
13
+
14
+ build_containers () {
15
+ docker compose build;
16
+ }
17
+
18
+ start_containers() {
19
+ docker compose up -d;
20
+ }
21
+
22
+ stop_containers() {
23
+ docker compose down;
24
+ }
25
+
26
+ case "$1" in
27
+ start)
28
+ start_containers
29
+ ;;
30
+ stop)
31
+ stop_containers
32
+ ;;
33
+ build)
34
+ stop_containers
35
+ build_containers
36
+ start_containers
37
+ ;;
38
+ *)
39
+ echo "Usage: $0 {build|start|stop}"
40
+ exit 1
41
+ ;;
42
+ esac;
43
+
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # this script imports AnnotationLists from a directory
4
+ # in an aiiinotate instance. the directory must only contain
5
+ # AnnotationLists, with all files at the root of the directory.
6
+ #
7
+ # USAGE: bash docker_aiiinotate_import.sh <"annotations"|"manifests"> <path/to/directory>
8
+
9
+ # "annotation" or "manifest": the type of data to import.
10
+ DATATYPE="$1"
11
+
12
+ # directory containing all files we want to import into aiiinotate
13
+ HOST_DIR=$(echo "$2" | sed -e "s~/$~~g") # sed removes trailing "/"
14
+
15
+ CONTAINER="docker-aiiinotate-1"
16
+
17
+ EXEC="docker exec $CONTAINER"
18
+ NODE_BIN=$($EXEC realpath node_modules/.bin)
19
+ DOTENVX_BIN=$($EXEC realpath $NODE_BIN/dotenvx)
20
+ AIIINOTATE_BIN=$($EXEC realpath $NODE_BIN/aiiinotate)
21
+
22
+ # .env file in container.
23
+ CONTAINER_ENV_FILE=$($EXEC realpath .env)
24
+ # directory where files in $IMPORT_DIR will be stored in the container.
25
+ CONTAINER_DIR="import_files"
26
+ # file listing absolute paht to contents of $CONTAINER_DIR
27
+ CONTAINER_INDEX_FILE=import_files.txt
28
+
29
+ [[ "$DATATYPE" != "annotation" && "$DATATYPE" != "manifest" ]] && {
30
+ echo "error: DATATYPE must be 'annotation' or 'manifest' (got: '$DATATYPE')"
31
+ exit 1
32
+ }
33
+
34
+ [ ! -d "$HOST_DIR" ] && {
35
+ echo "error: import directory '$HOST_DIR' not found ! exiting";
36
+ exit 1
37
+ }
38
+
39
+ # recreate CONTAINER_DIR
40
+ $EXEC bash -c "[ -d \"$CONTAINER_DIR\" ] && rm -rf \"$CONTAINER_DIR\"";
41
+ $EXEC mkdir "$CONTAINER_DIR"
42
+
43
+ # covert paths to abspath
44
+ CONTAINER_DIR=$($EXEC realpath "$CONTAINER_DIR")
45
+ CONTAINER_INDEX_FILE=$($EXEC realpath "$CONTAINER_INDEX_FILE")
46
+
47
+ # copy all files in $HOST_DIR to the docker in $CONTAINER_DIR.
48
+ # if a file in $HOST_DIR is not found, exit.
49
+ for fp in "$HOST_DIR"/*; do
50
+ [ ! -f "$fp" ] && {
51
+ echo "file not found on host: '$fp'. exiting.";
52
+ exit 1
53
+ }
54
+ docker container cp "$fp" "$CONTAINER":"$CONTAINER_DIR"
55
+ done
56
+
57
+ $EXEC bash -c "echo \$(find \"$CONTAINER_DIR\"/* -type f | xargs readlink -f > import_files.txt)"
58
+ $EXEC cat "$CONTAINER_INDEX_FILE"
59
+
60
+ $EXEC bash -c \
61
+ "$DOTENVX_BIN run -f \"$CONTAINER_ENV_FILE\" -- \
62
+ \"$AIIINOTATE_BIN\" import \"$DATATYPE\" -i 2 -f \"$CONTAINER_INDEX_FILE\""
package/docs/api.md ADDED
@@ -0,0 +1,381 @@
1
+ # API
2
+
3
+ This document describes **aiiinotate**'s API.
4
+
5
+ ## Introductory notes
6
+
7
+ ### Terminology
8
+
9
+ In the docs below,
10
+
11
+ - `Parameters` describes route parameters (dynamic segments of a route's URL).
12
+ - `Query` describes the query string in a key-value format
13
+
14
+ ### IIIF Version
15
+
16
+ **aiiinotate** is meant to be able to handle both IIIF presentation APIs: the most common [2.x](https://iiif.io/api/presentation/2.1) and the more recent [3.x](https://iiif.io/api/presentation/3.0). Both APIs define a data structure for manifests, annotations, lists of annotations and collections of manifests.
17
+
18
+ **HOWEVER, in aiiinotate, v2 and v3 data are isolated**: they form two separate collections, and no conversion is done between IIIF 2.x and 3.x data. This means that:
19
+ - **when communicating with aiiinotate**, you must specify a **IIIF presentation version in the query URL**. In the docs, this is described by the `iiif_version` keyword.
20
+ - **when inserting/updating data**, the data structure you provide must match the URL's `iiif_version`: you can't insert an annotation in v3 if your `iiif_version` is `2`.
21
+ - **TLDR**:
22
+ - your data must match the `iiif_version` argument
23
+ - if you insert an Annotation following the API v3.x, you can't search for it using `iiif_version=2`.
24
+
25
+ This is because
26
+ - the IIIF standard is quite complex and there are breaking changes between v2 and v3
27
+ - handling conversions between v2 and v3 is error prone, would increase calculations and slow the app down
28
+
29
+ ---
30
+
31
+ ## Generic routes
32
+
33
+ ### IIIF search API
34
+
35
+ ```
36
+ GET /search-api/{iiif_version}/manifests/{manifest_short_id}/search
37
+ ```
38
+
39
+ Implementation of the [IIIF Search API](https://iiif.io/api/search/2.0/), to search one or several annotations within a manifest.
40
+
41
+ #### Request
42
+
43
+ - Parameters:
44
+ - `iiif_version` (`2 | 3`): the IIIF aearch API version. 2 is for IIIF Presentation API 3.x, 1 is for IIIF Presentation API 2.x
45
+ - `manifest_short_id` (`string`): the ID of the manifest. See the *IIIF URIs* section.
46
+ - Query:
47
+ - `q` (`string`): query string.
48
+ - if `iiif_version=1`, `q` is searched in the fields: `@id`, `resource.@id` or `resource.chars` fields
49
+ - `motivation` (`painting | non-painting | commenting | describing | tagging | linking`): values for the `motivation` field of an annotation
50
+ - `canvasMin` (`number`): a positive integer
51
+ - `canvasMax` (`number`): a positive integer
52
+ - `canvasMax` must be greater than `canvasMin`
53
+ - if `canvasMax` is undefined, then we will only return the annotations that target a canvas at the `canvasMin` position in its manifest.
54
+ - `page` (`number`): results are paginated. This specifies the page number
55
+ - `pageSize` (`number`): number of annotations to display per page. Defaults to `process.env.AIIINOTATE_PAGE_SIZE`.
56
+ - `onlyIds` (`boolean`): return just the value of `@id` fields of matched annotations as a `string[]` instead of returning all the annotations. If `onlyIds=true`, there is no pagination, `page` and `pageSize` won't have any effect.
57
+
58
+ #### Response
59
+
60
+ ```
61
+ AnnotationList | AnnotationPage | string[]
62
+ ```
63
+
64
+ - if `onlyIds=true`, return a `string[]` (array of the IDs of all matched annotations)
65
+ - otherwise,
66
+ - if `iiif_version` is `1`, return an `AnnotationList`
67
+ - else, return an `AnnotationPage`.
68
+
69
+ #### Notes
70
+
71
+ - `canvasMin`, `canvasMax` and `onlyIds` are non-standard query parameters that are NOT part of the IIIF Search API specification.
72
+ - If `q` and `motivation` are unused, it will return all annotations for the manifest
73
+ - Only exact matches are allowed for `q` and `motivation`
74
+ - About `canvasMin` and `canvasMax`:
75
+ - they are used to search for annotations that target a range of canvases: for example, fetch all anotations between pages 3 and 30 of a manuscript.
76
+ - they are **0-indexed**: the 1st canvas of a manifest is indexed `0`, the 2nd is indexed `1`...
77
+ - See section [Create/update an annotation](#createupdate-an-annotation) for more information and possible issues with canvas indexes.
78
+
79
+ ---
80
+
81
+ ### Delete an annotation or a manifest
82
+
83
+ ```
84
+ DELETE /{collection_name}/{iiif_version}/delete
85
+ ```
86
+
87
+ #### Request
88
+
89
+ - Parameters:
90
+ - `collection_name` (`annotations | manifests`): delete an annotation or a manifest
91
+ - `iiif_version` (`2 | 3`): IIIF presentation version
92
+ - Query:
93
+ - if `collection_name = manifests`:
94
+ - `uri`: the full URI of the manifest to delete
95
+ - `manifestShortId`: the manifest's identifier
96
+ - if `collection_name = annotation`:
97
+ - `uri`: the full URI of the annotation to delete
98
+ - `manifestShortId`: a manifest's identifier, to delete all annotations for a manifest
99
+ - `canvasUri`: the full URI to an annotation's target canvas, to delete all annotatons for the canvas
100
+ - `tag`: delete all annotations with a certain tag in a manifest
101
+ - MUST be used in conjunction with `manifestShortId` to indicate on which manifest to delete annotations by tag: `delete?tag=<your-tag>&manifestShortId=<your-manifest-id>`
102
+
103
+ #### Response
104
+
105
+ ```
106
+ { deletedCount: <integer> }
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Manifests routes
112
+
113
+ ### Get an index of all manifests
114
+
115
+ ```
116
+ GET /manifests/{iiif_version}
117
+ ```
118
+
119
+ Returns a Collection of all manifests in your **aiiinotate** instance.
120
+
121
+ #### Request
122
+
123
+ - Parameters:
124
+ - `iiif_version` (`2 | 3`): the IIIF Presentation API version
125
+
126
+ #### Response
127
+
128
+ A IIIF `Collection`, following the IIIF Presentation API 2 or 3, depending of the value of `iiif_version`.
129
+
130
+ ---
131
+
132
+ ### Insert a manifest
133
+
134
+ ```
135
+ POST /manifests/{iiif_version}/create
136
+ ```
137
+
138
+ #### Request
139
+
140
+ - Parameters:
141
+ - `iiif_version` (`2 | 3`): the IIIF Presentation API version of your manifest
142
+ - Body (`JSON`): the manifest to index in the database
143
+
144
+ #### Response
145
+
146
+ ```
147
+ {
148
+ insertedIds: string[],
149
+ preExistingIds: string[],
150
+ rejectedIds: []
151
+ }
152
+ ```
153
+
154
+ - `insertedIds`: the list of IDs of inserted manifests
155
+ - `preExisingIds`: the IDs of manifests that were aldready in the database
156
+ - `rejectedIds`: the IDs of manifests on which an error occurred
157
+
158
+ ---
159
+
160
+ ## Annotation routes
161
+
162
+ ### Get all annotations for a canvas
163
+
164
+ ```
165
+ GET /annotations/{iiif_version}/search
166
+ ```
167
+
168
+ #### Request
169
+
170
+ - Parameters:
171
+ - `iiif_version` (`2 | 3`): the IIIF Presentation API of your manifests
172
+ - Query:
173
+ - `canvasUri` (`string`): the URI of the target canvas
174
+ - `page` (`number`): results are paginated. Specifies the page number.
175
+ - `pageSize` (`number`): number of items per page. Defaults to `process.env.AIIINOTATE_PAGE_SIZE`.
176
+
177
+ #### Response
178
+
179
+ Results are paginated.
180
+
181
+ ```
182
+ AnnotationList | AnnotationPage
183
+ ```
184
+
185
+ ---
186
+
187
+ ### Count annotations
188
+
189
+ ```
190
+ GET /annotations/{iiif_version}/count
191
+ ```
192
+
193
+ #### Request
194
+
195
+ - Parameters:
196
+ - `iiif_version` (`2 | 3`): the IIIF Presentation API of your manifests
197
+ - Query:
198
+ - `uri` (`string`): the annotation's `@id`
199
+ - `canvasUri` (`string`): the annotation's target canvas (`on.full`)
200
+ - `manifestShortId` (`string`): the short ID of the annotation's target manifest (`on.manifestShortId`)
201
+
202
+ #### Response
203
+
204
+ ```
205
+ { count: integer }
206
+ ```
207
+
208
+ ---
209
+
210
+ ### Get a single annotation
211
+
212
+ ```
213
+ GET /data/{iiif_version}/{manifest_short_id}/annotation/{annotation_short_id}
214
+ ```
215
+
216
+ This route allows to query an annotation by its ID by defering its `@id | id` field. This URL follows the IIIF specification
217
+
218
+ #### Request
219
+
220
+ - Parameters:
221
+ - `iiif_version` (`2 | 3`): the IIIF version of the annotation
222
+ - `manifest_short_id` (`string`): the identifier of the manifest the annotation is related to
223
+ - `annotation_short_id`: the unique part of the annotation URL
224
+
225
+ #### Response
226
+
227
+ `Object`: the annotation. Its format follows the IIIF Presentation specification 2 or 3, based on the value of `iiif_version`.
228
+
229
+ ---
230
+
231
+ ### Create/update an annotation
232
+
233
+ ```
234
+ POST /annotations/{iiif_version}/{action}
235
+ ```
236
+
237
+ Create or update a single annotation
238
+
239
+ #### Request
240
+
241
+ - Parameters:
242
+ - `iiif_version` (`2 | 3`): the IIIF version of the annotation
243
+ - `action` (`create | update`): the action to perform: create or update an annotation
244
+ - Query:
245
+ - `throwOnCanvasIndexError` (`boolean`): throw an error if there's a problem fetching the annotation's target canvas index. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
246
+ - `throwOnXywhError` (`boolean`): throw an error if target bounding box calculation fails. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
247
+ - Body (`Object`): a IIIF annotation that follows the IIIF Presentation API 2 or 3 (depending on the value of `iiif_version`)
248
+
249
+ #### Response
250
+
251
+ ```
252
+ {
253
+ insertedIds: string[],
254
+ preExistingIds: string[],
255
+ rejectedIds: string[],
256
+ fetchErrorIds: string[]
257
+ }
258
+ ```
259
+
260
+ ---
261
+
262
+ ### Insert several annotations
263
+
264
+ ```
265
+ POST /annotations/{iiif_version}/createMany
266
+ ```
267
+
268
+ Batch insert multiple annotations.
269
+
270
+ #### Request
271
+
272
+ - Parameters:
273
+ - `iiif_version` (`2 | 3`): the IIIF version of the annotation
274
+ - Query:
275
+ - `throwOnCanvasIndexError` (`boolean`): throw an error if there's a problem fetching the annotation's target canvas index. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
276
+ - `throwOnXywhError` (`boolean`): throw an error if target bounding box calculation fails. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
277
+ - Body: either:
278
+ - a full `AnnotationList | AnnotationPage` embedded in the body (type must match `iiif_version`: AnnotationPage for IIIF 3, AnnotationList for IIIF 2).
279
+ - `AnnotationList[] | AnnotationPage[]` (type must match `iiif_version`): an array of annotation lists or pages
280
+ - `{ uri: string }`: an object containing a reference to an `AnnotationList` or `AnnotationPage`
281
+ - `{ uri: string }[]`: an array of objects containing a reference to an `AnnotationList` or `AnnotationPage`.
282
+
283
+ #### Response
284
+
285
+ ```
286
+ {
287
+ insertedIds: string[],
288
+ preExistingIds: string[],
289
+ rejectedIds: []
290
+ }
291
+ ```
292
+
293
+ #### Notes
294
+
295
+ - Calling this route using parallel processes (i.e., `Promise.all`) can cause data races which will cause inserts to fail
296
+ - Be wary of maximum body size, especially when sending AnnotationLists in your body. If possible, using `{ uri: string }` is better.
297
+ - All annotations within a single AnnotationList/Page may have different target canvases or manifests.
298
+ - See **Create/update an annotation**.
299
+
300
+ ---
301
+
302
+ ## Appendix 1: annotation canvas index and bounding box calculation
303
+
304
+ Two non-standard side-effects happen when inserting an annotation:
305
+ - the annotation's target manifest is also fetched and inserted in the database. The annotation's target canvas index is fetched and added to the annotation.
306
+ - calculating the XYWH bounding box of an annotation.
307
+
308
+ Annotations in `aiiinotate` contain nonstandard fields. In IIIF presentation 2.x,
309
+ - `manifestUri`, `manifestShortId`, `canvasIdx`:
310
+ - `annotation.on.manifestUri`: the URI of the manifest on which is an annotation
311
+ - `annotation.on.manifestShortId`: the unique identifier of the manifest on which is an annotation
312
+ - `annotation.on.canvasIdx`: the position of an annotation's target canvas within the target manifest, as an integer. `canvasIdx` is **0-indexed**: the 1st canvas in a manifest is indexed from `0`
313
+ - this relies on reconstructing an annotation's target manifest URL and fetching it. If this process fails, the fields above will be `undefined`.
314
+ - fetching an annotation's target manfest is error prone: it depends on the manifest being available through HTTP, which is not in our control.
315
+ - **you can control error throwing** (throw an error if a canvas index can't be found) using:
316
+ - at route-level, the `throwOnCanvasIndexError` query parameter
317
+ - at app-level, the `AIIINOTATE_STRICT_MODE` env variable.
318
+ - if `AIIINOTATE_STRICT_MODE` or `throwOnCanvasIndexError`, an error will be thrown if an error appears anywhere in the process of fetching the target manifest and populating the `canvasIdx` field.
319
+ - `annotation.on.xywh`: the annotation's bounding box on its target canvas.
320
+ - when inserting an annotation, aiiinotate will attempt to extract its XYWH bounding box and store it as an `[number,number,number,number]`.
321
+ - this only works for `oa:SvgSelectors`, `oa:FragmentSeletors`, `oa:Choice` containing either an `SvgSelector` or a `FragmentSelector`, or a string-target (an URI with a `#xywh=` fragment selector).
322
+ - **you can control error-throwing** (throw an error if a bounding-box can't be calculated) using:
323
+ - at route-level, the `throwOnXywhError` query parameter
324
+ - at app-level, the `AIIINOTATE_STRICT_MODE` env variable.
325
+
326
+ About `throwOnCanvasIndexError`, `throwOnXywhError` and `AIIINOTATE_STRICT_MODE`:
327
+ - as indicated above, fetching an annotation's target canvas index and calculating an annotation's target bounding boxc is error prone.
328
+ - in turn, you can decide wether or not to throw errors using:
329
+ - at route-level, the `throwOnCanvasIndexError` (for canvas index) and `throwOnXywhError` (for bounding box) query parameters
330
+ - at app-level, the `AIIINOTATE_STRICT_MODE` env variable. Setting this to `true` will set the value of `throwOnCanvasIndexError` and `throwOnXywhError` to `true` by default.
331
+ - the value of `AIIINOTATE_STRICT_MODE` can be overridden by setting a value to query parameters `throwOnCanvasIndexError` and `throwOnXywhError`.
332
+ - if those are set to `true`, an error will be thrown if a canvas index can't be found. Otherwise, the values will be set to `undefined`.
333
+ - in controlled environments where you know your manifests will be available and where you rely heavily on the `canvasIdx` field (like AIKON), throwing an error will ensure that the `canvasIdx` field is always defined.
334
+
335
+ ---
336
+
337
+ ## Appendix 2: App logic: URL prefixes
338
+
339
+ URL anatomy is a mix of [SAS endpoints](./specifications/4_sas.md) and IIIF specifications. In turn, we define the following prefixes:
340
+
341
+ - `data`: for all IIIF URIs: URIs of annotations and annotation lists
342
+ - `annotations`: operations on annotations
343
+ - `manifests`: operations on manifests
344
+ - `search-api`: endpoint to access the IIIF search API
345
+
346
+ In turn, URL anatomy is:
347
+
348
+ ```
349
+ {host}/{prefix}/{iiif_version}/{slug}
350
+ ```
351
+
352
+ Where:
353
+ - `host`: the host of your app
354
+ - `prefix`: `data | annotations | manifests`
355
+ - `iiif_version`:
356
+ - if `prefix` is `search-api`, `1 | 2`: IIIF Search API version used
357
+ - otherwise, `2 | 3`, the IIIF Presentation API version your data is in
358
+ - `slug`: the rest of the qurty URI
359
+
360
+ There is an extra URL prefix: `schemas`. It is only used internally (not accessible to clients or accessible through HTTP) to define the IDs of all JsonSchemas, so we won't talk about it here.
361
+
362
+
363
+ ---
364
+
365
+ ## Appendix 3: IIIF URIs
366
+
367
+ IIIF URIs in the Presentation 2.1 API are:
368
+
369
+ ```
370
+ Collection {scheme}://{host}/{prefix}/collection/{name}
371
+ Manifest {scheme}://{host}/{prefix}/{manifest_short_id}/manifest
372
+ Sequence {scheme}://{host}/{prefix}/{manifest_short_id}/sequence/{name}
373
+ Canvas {scheme}://{host}/{prefix}/{manifest_short_id}/canvas/{name}
374
+ Annotation (incl images) {scheme}://{host}/{prefix}/{manifest_short_id}/annotation/{name}
375
+ AnnotationList {scheme}://{host}/{prefix}/{manifest_short_id}/list/{name}
376
+ Range {scheme}://{host}/{prefix}/{manifest_short_id}/range/{name}
377
+ Layer {scheme}://{host}/{prefix}/{manifest_short_id}/layer/{name}
378
+ Content {scheme}://{host}/{prefix}/{manifest_short_id}/res/{name}.{format}
379
+ ```
380
+
381
+
package/docs/cli.md ADDED
@@ -0,0 +1,132 @@
1
+ # CLI DOCUMENTATION
2
+
3
+ The CLI is the main interface to interact with aiiinotate: all aiiinotate commands are started from the CLI.
4
+
5
+ ---
6
+
7
+ ## Basics
8
+
9
+ 1. **Have your app set up**, either in dev or in prod. See [README](https://github.com/Aikon-platform/aiiinotate/blob/main/README.md) for more info
10
+
11
+ 2. **In prod, source your `.env`**. See README.
12
+
13
+ 3. **The base command is**:
14
+
15
+ ```bash
16
+ # in prod
17
+ aiiinotate
18
+
19
+ # in dev
20
+ npm run cli
21
+ ```
22
+
23
+ **The command changes with a prod install**, because the package has been installed with `npm`.
24
+ - for the rest of this doc, we use `aiiinotate` instead of `npm run cli`. Use whichever is appropriate to your use case.
25
+ - `npm run cli` runs a bash script that also manages environment sourcing.
26
+ - when using `npm run cli`, you will sometimes need to add `--` to separate different arguments:
27
+ ```bash
28
+ npm run cli -- <command> <arguments>
29
+ ```
30
+
31
+ ---
32
+
33
+ ## `serve`: start the app
34
+
35
+ ```bash
36
+ # serve the app in dev mode
37
+ aiiinotate serve dev
38
+
39
+ # serve the app in prod mode
40
+ aiiinotate serve prod
41
+ ```
42
+
43
+ ---
44
+
45
+ ## `migrate`: run database migrations
46
+
47
+ NOTE: **in dev**, run `npm run migrate`, which is a wrapper for `npm run cli migrate`.
48
+
49
+ ```bash
50
+ # create a new migration
51
+ aiiinotate migrate make --migration-name <your migration name>
52
+ # the equivalent in dev is:
53
+ npm run migrate make -- --migration-name <your migration name>
54
+
55
+ # apply all pending migrations
56
+ aiiinotate migrate apply
57
+
58
+ # revert the last migration
59
+ aiiinotate migrate revert
60
+
61
+ # revert all migrations
62
+ aiiinotate migrate revert-all
63
+ ```
64
+
65
+ ---
66
+
67
+ ## `import`: import data
68
+
69
+ The CLI can be used to import IIIF annotations or to index IIIF manifests in aiiinotate.
70
+
71
+ ```bash
72
+ aiiinotate import <manifests|annotations> -i 2 -f <path/to/import/file.txt>
73
+ ```
74
+
75
+ Where:
76
+ - `<manifests|annotations>` is the kind of data to import: manifests or annotations
77
+ - `-i` `--iiif-version` (`2|3`) is the IIIF Presentation API version of the data to import:
78
+ - if `-i 3`, we import IIIF presentation 3.x data
79
+ - if `-i 2`, we import IIIF presentation 2.x data
80
+ - `-f --file` is a relative or absolute path the the import file. This import file:
81
+ - contains a list of paths to annotation lists, annotation pages or manifests
82
+ - with 1 path per line
83
+ - paths can be relative or absolute
84
+
85
+ NOTE that data to import **MUST MATCH the IIIF version**: if `-i 2`, you can only import `AnnotationLists` following the IIIF Presentation 2.x standard, and note annotations in 3.x format.
86
+
87
+ NOTE that for the moment, import is only implemented for IIIF version 2.
88
+
89
+ Here is an example of import file:
90
+
91
+ ```
92
+ ./annotations/wit91_pdf108_anno194.json
93
+ ./annotations/wit70_man70_anno206.json
94
+ ./annotations/wit71_man71_anno202.json
95
+ ./annotations/wit68_man68_anno198.json
96
+ ./annotations/wit72_man72_anno133.json
97
+ ./annotations/wit75_man74_anno130.json
98
+ ./annotations/wit2_man4_anno4.json
99
+ ```
100
+
101
+ ---
102
+
103
+ ## `export`: export data
104
+
105
+ The CLI can be used to export data to JSON.
106
+
107
+ ```bash
108
+ aiiinotate export <all|annotations2|manifests2|annotations3|manifests3> [-o /output/dir]?
109
+ ```
110
+
111
+ Each collection will be written to its own JSON file.
112
+
113
+ Exported JSON data can be imported back in a MongoDB instance using [mongoimport](https://www.mongodb.com/docs/database-tools/mongoimport/):
114
+
115
+ ```bash
116
+ mongoimport \
117
+ --host localhost \
118
+ --db <db name> \
119
+ --collection <collection name> \
120
+ --file <./path/to/export.json> \
121
+ --jsonArray
122
+ ```
123
+
124
+ ---
125
+
126
+ ## `xywhToInt`
127
+
128
+ In versions of aiiinotate `>0.10.3`, the `xywh` bounding box extracted from incoming annotations and saved to database may contain `floats`. This causes errors when reconstructing Cantaloupe URLs from t`xywh`. This command converts all `xywh` containing floats to intgers. It is only implemented for collection `annotations2`.
129
+
130
+ ```bash
131
+ aiiinotate run xywh-to-int
132
+ ```