aiiinotate 0.2.15 → 0.3.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.
- package/docker/docker.sh +1 -1
- package/docs/architecture.md +12 -12
- package/package.json +3 -3
- package/scripts/setup_mongodb.sh +10 -1
- package/scripts/utils.sh +65 -0
- package/src/app.js +2 -2
- package/src/data/annotations/annotations2.js +32 -30
- package/src/data/annotations/routes.js +2 -1
- package/src/data/annotations/routes.test.js +4 -4
- package/src/data/manifests/manifests2.test.js +1 -1
- package/src/data/manifests/routes.test.js +1 -1
- package/src/data/routes.test.js +5 -5
- package/src/data/utils/iiif2Utils.js +38 -30
- package/src/data/utils/iiif2Utils.test.js +1 -1
- package/src/{fileServer → fixtures}/annotations.js +3 -3
- package/src/{fileServer → fixtures}/index.js +8 -8
- package/src/{fileServer → fixtures}/manifests.js +1 -1
- package/src/{fileServer → fixtures}/utils.js +3 -3
- package/src/schemas/schemasPresentation2.js +9 -6
- package/src/schemas/schemasRoutes.js +28 -2
- /package/src/{fileServer → fixtures}/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +0 -0
- /package/src/{fileServer → fixtures}/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +0 -0
- /package/src/{fileServer → fixtures}/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +0 -0
- /package/src/{fileServer → fixtures}/data/annotations2Invalid.jsonld +0 -0
- /package/src/{fileServer → fixtures}/data/annotations2Valid.jsonld +0 -0
- /package/src/{fileServer → fixtures}/data/bnf_invalid_manifest.json +0 -0
- /package/src/{fileServer → fixtures}/data/bnf_valid_manifest.json +0 -0
- /package/src/{fileServer → fixtures}/data/vhs_wit253_man253_anno253_anno-24.json +0 -0
package/docker/docker.sh
CHANGED
|
@@ -45,7 +45,7 @@ env_to_docker() {
|
|
|
45
45
|
# NOTE that the MongoDB host in Docker MUST BE the name of the Mongo docker service (defined in docker-compose)
|
|
46
46
|
cp "$ENV_CONFIG" "$ENV_DOCKER";
|
|
47
47
|
sed_repl s~^MONGODB_HOST=.*$~MONGODB_HOST=mongo~ "$ENV_DOCKER";
|
|
48
|
-
sed_repl s~^AIIINOTATE_HOST=.*~AIIINOTATE_HOST=0.0.0.0~ "$ENV_DOCKER"
|
|
48
|
+
sed_repl s~^AIIINOTATE_HOST=.*~AIIINOTATE_HOST=0.0.0.0~ "$ENV_DOCKER"
|
|
49
49
|
}
|
|
50
50
|
env_to_docker;
|
|
51
51
|
|
package/docs/architecture.md
CHANGED
|
@@ -6,19 +6,19 @@ This is a high-level overview of the Aiiinotate and a good place to start if you
|
|
|
6
6
|
|
|
7
7
|
## Top level plugins
|
|
8
8
|
|
|
9
|
-
Fastify uses a [**plugin system**](https://fastify.dev/docs/latest/Guides/Plugins-Guide/). In short, it means that,
|
|
9
|
+
Fastify uses a [**plugin system**](https://fastify.dev/docs/latest/Guides/Plugins-Guide/). In short, it means that,
|
|
10
10
|
- to access the global `fastify` instance, your code needs to be a plugin
|
|
11
11
|
- all plugins must export a single function that registers the plugin on the global fastify instance
|
|
12
12
|
|
|
13
|
-
This can be quite convoluted and conflict with a "normal" module architecture, so we do a mix of the two:
|
|
13
|
+
This can be quite convoluted and conflict with a "normal" module architecture, so we do a mix of the two:
|
|
14
14
|
- **all modules at the root** of `src/` are plugins (except `config`)
|
|
15
|
-
- **nested plugins** within each of the above are defined when they need to access the fastify instance.
|
|
15
|
+
- **nested plugins** within each of the above are defined when they need to access the fastify instance.
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
./src/
|
|
18
|
+
./src/fixtures/ - module storing and serving data files for test fixtures
|
|
19
19
|
./src/schemas/ - JsonSchemas definition and validation
|
|
20
20
|
./src/db/ - connects the app to the mongojs database
|
|
21
|
-
./src/data/ - routes and modules to read/write data
|
|
21
|
+
./src/data/ - routes and modules to read/write data
|
|
22
22
|
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -38,13 +38,13 @@ Each `route` and `class` is a fastify plugin. Since the `data` plugin is registe
|
|
|
38
38
|
|
|
39
39
|
```
|
|
40
40
|
├── index.js // `data` plugin root
|
|
41
|
-
├── routes.js // generic routes
|
|
41
|
+
├── routes.js // generic routes
|
|
42
42
|
├── annotations
|
|
43
43
|
│ ├── annotations2.js // plugin for IIIF presentation 2 annotations
|
|
44
|
-
│ ├── annotations3.js // plugin for IIIF presentation 3 annotations
|
|
44
|
+
│ ├── annotations3.js // plugin for IIIF presentation 3 annotations
|
|
45
45
|
│ ├── routes.js // routes for both plugins
|
|
46
46
|
├── collectionAbstract.js // abstract class with functionnalities for all plugins
|
|
47
|
-
├── manifests
|
|
47
|
+
├── manifests
|
|
48
48
|
├── manifests2.js // plugin for IIIF presentation 2 manifests
|
|
49
49
|
├── manifests3.js // plugin for IIIF presentation 3 manifests
|
|
50
50
|
├── routes.js // routes for both plugins
|
|
@@ -57,9 +57,9 @@ At a high level, `route` files receive data and relegate internal mongoJS intera
|
|
|
57
57
|
|
|
58
58
|
Our 5 `collection classes` (`collectionAbstract`, `annotations2`, `annotations3`, `manifests2`, `manifests3`) use class inheritence: all classes inherit from `collectionAbstract`
|
|
59
59
|
- `collectionAbstract` defines collection-agnostic processes that can be used by all other classes.
|
|
60
|
-
- other classes implement functionnalities for a specific data type and collection.
|
|
60
|
+
- other classes implement functionnalities for a specific data type and collection.
|
|
61
61
|
|
|
62
|
-
---
|
|
62
|
+
---
|
|
63
63
|
|
|
64
64
|
## Details: why plugins are complicated ?
|
|
65
65
|
|
|
@@ -69,13 +69,13 @@ If using a plugin-only architecture, **you should not do "local" imports** (impo
|
|
|
69
69
|
// here, we define a root plugin with 2 subplugins (1st is a decorator, 2nd a normal plugin).
|
|
70
70
|
fastify.register((instance, opts, done) => {
|
|
71
71
|
|
|
72
|
-
// decorator
|
|
72
|
+
// decorator
|
|
73
73
|
instance.decorate('util', (a, b) => a + b)
|
|
74
74
|
console.log(instance.util('that is ', 'awesome'))
|
|
75
75
|
|
|
76
76
|
// plugin
|
|
77
77
|
fastify.register((instance, opts, done) => {
|
|
78
|
-
console.log(instance.util('that is ', 'awesome'))
|
|
78
|
+
console.log(instance.util('that is ', 'awesome'))
|
|
79
79
|
done()
|
|
80
80
|
})
|
|
81
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiiinotate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "a fast IIIF-compliant annotation server",
|
|
5
5
|
"main": "./cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"#config/*.js": "./config/*.js",
|
|
48
48
|
"#data/*.js": "./src/data/*.js",
|
|
49
49
|
"#utils/*.js": "./src/data/utils/*.js",
|
|
50
|
-
"#
|
|
51
|
-
"#
|
|
50
|
+
"#fixtures/*.js": "./src/fixtures/*.js",
|
|
51
|
+
"#fixtures/*.json": "./src/fixtures/*.json",
|
|
52
52
|
"#manifests/*.js": "./src/data/manifests/*.js",
|
|
53
53
|
"#annotations/*.js": "./src/data/annotations/*.js"
|
|
54
54
|
},
|
package/scripts/setup_mongodb.sh
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
#!/bin/env bash
|
|
2
2
|
|
|
3
|
+
set -e
|
|
4
|
+
|
|
3
5
|
# mongo install guide: https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/#std-label-install-mdb-community-ubuntu
|
|
4
6
|
|
|
5
7
|
#NOTE only the mongodb installation is done here. for database creation, see `setup_mongodb_populate.sh`
|
|
6
8
|
|
|
7
9
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
|
8
|
-
source "$SCRIPT_DIR/utils.sh"
|
|
10
|
+
source "$SCRIPT_DIR/utils.sh"
|
|
11
|
+
|
|
12
|
+
# float arithmetic comparison is not supported by bash and we need to use `bc`
|
|
13
|
+
# usage: if float_comparison "a >= b"; then... ; fi
|
|
14
|
+
float_comparison () {
|
|
15
|
+
expr="$1"
|
|
16
|
+
(( $(echo "$expr" |bc -l) ));
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
install_mongodb_ubuntu () {
|
|
11
20
|
|
package/scripts/utils.sh
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
color_echo() {
|
|
4
|
+
Color_Off="\033[0m"
|
|
5
|
+
Red="\033[1;91m" # Red
|
|
6
|
+
Green="\033[1;92m" # Green
|
|
7
|
+
Yellow="\033[1;93m" # Yellow
|
|
8
|
+
Blue="\033[1;94m" # Blue
|
|
9
|
+
Purple="\033[1;95m" # Purple
|
|
10
|
+
Cyan="\033[1;96m" # Cyan
|
|
11
|
+
|
|
12
|
+
case "$1" in
|
|
13
|
+
"green") echo -e "$Green$2$Color_Off";;
|
|
14
|
+
"red") echo -e "$Red$2$Color_Off";;
|
|
15
|
+
"blue") echo -e "$Blue$2$Color_Off";;
|
|
16
|
+
"yellow") echo -e "$Yellow$2$Color_Off";;
|
|
17
|
+
"purple") echo -e "$Purple$2$Color_Off";;
|
|
18
|
+
"cyan") echo -e "$Cyan$2$Color_Off";;
|
|
19
|
+
*) echo "$2";;
|
|
20
|
+
esac
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
echo_title(){
|
|
24
|
+
sep_line="========================================"
|
|
25
|
+
len_title=${#1}
|
|
26
|
+
|
|
27
|
+
if [ "$len_title" -gt 40 ]; then
|
|
28
|
+
sep_line=$(printf "%0.s=" $(seq 1 $len_title))
|
|
29
|
+
title="$1"
|
|
30
|
+
else
|
|
31
|
+
diff=$((38 - len_title))
|
|
32
|
+
half_diff=$((diff / 2))
|
|
33
|
+
sep=$(printf "%0.s=" $(seq 1 $half_diff))
|
|
34
|
+
|
|
35
|
+
if [ $((diff % 2)) -ne 0 ]; then
|
|
36
|
+
title="$sep $1 $sep="
|
|
37
|
+
else
|
|
38
|
+
title="$sep $1 $sep"
|
|
39
|
+
fi
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
color_echo purple "\n\n$sep_line\n$title\n$sep_line"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get_os() {
|
|
46
|
+
uname_out="$(uname -s)"
|
|
47
|
+
case "${uname_out}" in
|
|
48
|
+
Linux*) os=Linux;;
|
|
49
|
+
Darwin*) os=Mac;;
|
|
50
|
+
CYGWIN*) os=Cygwin;;
|
|
51
|
+
MINGW*) os=MinGw;;
|
|
52
|
+
MSYS_NT*) os=Git;;
|
|
53
|
+
*) os="UNKNOWN:${unameOut}"
|
|
54
|
+
esac
|
|
55
|
+
echo "${os}"
|
|
56
|
+
}
|
|
57
|
+
export OS
|
|
58
|
+
OS=$(get_os)
|
|
59
|
+
|
|
60
|
+
# float arithmetic comparison is not supported by bash and we need to use `bc`
|
|
61
|
+
# usage: if float_comparison "a >= b"; then... ; fi
|
|
62
|
+
float_comparison () {
|
|
63
|
+
expr="$1"
|
|
64
|
+
(( $(echo "$expr" |bc -l) ));
|
|
65
|
+
}
|
package/src/app.js
CHANGED
|
@@ -6,7 +6,7 @@ import Fastify from "fastify";
|
|
|
6
6
|
import cors from "@fastify/cors";
|
|
7
7
|
// import swagger from "@fastify/swagger";
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import fixtures from "#fixtures/index.js";
|
|
10
10
|
import schemas from "#schemas/index.js";
|
|
11
11
|
import data from "#data/index.js";
|
|
12
12
|
import db from "#db/index.js";
|
|
@@ -101,7 +101,7 @@ async function build(mode="default") {
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
await fastify.register(db, mongoConfig);
|
|
104
|
-
await fastify.register(
|
|
104
|
+
await fastify.register(fixtures);
|
|
105
105
|
fastify.register(schemas);
|
|
106
106
|
fastify.register(data);
|
|
107
107
|
await fastify.ready();
|
|
@@ -106,18 +106,18 @@ class Annotations2 extends CollectionAbstract {
|
|
|
106
106
|
* @returns {object}
|
|
107
107
|
*/
|
|
108
108
|
#cleanAnnotation(annotation, update=false) {
|
|
109
|
-
// 1) extract ids and targets
|
|
109
|
+
// 1) extract ids and targets. convert the target to an array.
|
|
110
110
|
const
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
annotationTargetArray = makeTarget(annotation),
|
|
112
|
+
// we assume that all values of `annotationTargetArray` point to the same manifest => take the manifest of the 1st target
|
|
113
|
+
manifestShortId = annotationTargetArray[0].manifestShortId;
|
|
113
114
|
|
|
114
115
|
// in updates, "@id" has aldready been extracted
|
|
115
116
|
if ( !update ) {
|
|
116
117
|
annotation["@id"] = makeAnnotationId(annotation, manifestShortId);
|
|
117
118
|
}
|
|
118
119
|
annotation["@context"] = IIIF_PRESENTATION_2_CONTEXT["@context"];
|
|
119
|
-
annotation.on =
|
|
120
|
-
annotation.on.manifestShortId = manifestShortId;
|
|
120
|
+
annotation.on = annotationTargetArray;
|
|
121
121
|
|
|
122
122
|
// 2) process motivations.
|
|
123
123
|
// - motivations are an array of strings
|
|
@@ -170,26 +170,25 @@ class Annotations2 extends CollectionAbstract {
|
|
|
170
170
|
/**
|
|
171
171
|
* handle all side effects on the `manifests2` collection. this does 2 things:
|
|
172
172
|
* - insert all manifests referenced by `annotationData`, and set a key `on.manifestId` on all annotations.
|
|
173
|
-
* - set a key `on
|
|
173
|
+
* - set a key `canvasIdx` in all values of `annotation.on`, containing the position of the annotation's target canvas in the manifest,
|
|
174
174
|
* (or undefined if the manifest or canvas were not found).
|
|
175
175
|
* @param {object|object[]} annotationData - an annotation, or array of annotations.
|
|
176
176
|
*/
|
|
177
177
|
async #insertManifestsAndGetCanvasIdx(annotationData) {
|
|
178
178
|
// TODO : extract all canvas Ids, reconstruct manifest IDs from it. if they're valid, insert the manifests into the db.
|
|
179
179
|
// convert objects to array to get a uniform interface.
|
|
180
|
-
let converted
|
|
180
|
+
let converted
|
|
181
181
|
[ annotationData, converted ] = maybeToArray(annotationData, true);
|
|
182
182
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
// 1. get all distinct manifest URIs
|
|
184
|
+
const manifestUris = [];
|
|
185
|
+
annotationData.map((ann) => ann.on.map((target) => {
|
|
186
|
+
if ( target.manifestUri !== undefined && !manifestUris.includes(target.manifestUri) ) {
|
|
187
|
+
manifestUris.push(target.manifestUri);
|
|
188
|
+
}
|
|
189
|
+
}));
|
|
188
190
|
|
|
189
|
-
//
|
|
190
|
-
manifestUris = [...new Set(
|
|
191
|
-
annotationData.map((ann) => ann.on.manifestUri)
|
|
192
|
-
)];
|
|
191
|
+
// 2. insert the manifests
|
|
193
192
|
// NOTE: PERFORMANCE significantly drops because of this: test running for the entire app goes from ~1000ms to ~2600ms
|
|
194
193
|
const
|
|
195
194
|
insertResponse = await this.manifestsPlugin.insertManifestsFromUriArray(manifestUris, false),
|
|
@@ -197,19 +196,25 @@ class Annotations2 extends CollectionAbstract {
|
|
|
197
196
|
insertedManifestsIds = insertResponse.insertedIds.concat(insertResponse.preExistingIds || []);
|
|
198
197
|
|
|
199
198
|
// 3. update annotations with 2 things:
|
|
200
|
-
// - where manifest insertion has failed, set `
|
|
199
|
+
// - where manifest insertion has failed, set `manifestUri` to undefined on all values of `annotation.on`
|
|
201
200
|
// - set `annotation.on.canvasIdx`: the position of the target canvas within the manifest, or undefined if it cound not be found.
|
|
202
201
|
annotationData = await Promise.all(
|
|
203
202
|
annotationData.map(async (ann) => {
|
|
204
|
-
ann.on
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
203
|
+
ann.on = await Promise.all(
|
|
204
|
+
ann.on.map(async (target) => {
|
|
205
|
+
target.manifestUri =
|
|
206
|
+
// has the insertion of `manifestUri` worked ? (has it returned a valid response, woth `insertedIds` key).
|
|
207
|
+
insertedManifestsIds.find((x) => x === target.manifestUri)
|
|
208
|
+
? target.manifestUri
|
|
209
|
+
: undefined;
|
|
210
|
+
target.canvasIdx =
|
|
211
|
+
target.manifestUri
|
|
212
|
+
? await this.manifestsPlugin.getCanvasIdx(target.manifestUri, target.full)
|
|
213
|
+
: undefined;
|
|
214
|
+
// console.log(">>> #insertManifestsAndGetCanvasIdx: target", target);
|
|
215
|
+
return target;
|
|
216
|
+
})
|
|
217
|
+
)
|
|
213
218
|
return ann;
|
|
214
219
|
})
|
|
215
220
|
);
|
|
@@ -282,7 +287,6 @@ class Annotations2 extends CollectionAbstract {
|
|
|
282
287
|
: deleteKey==="canvasUri"
|
|
283
288
|
? { "on.full": deleteVal }
|
|
284
289
|
: { "on.manifestShortId": deleteVal };
|
|
285
|
-
|
|
286
290
|
return this.delete(deleteFilter);
|
|
287
291
|
}
|
|
288
292
|
|
|
@@ -391,9 +395,7 @@ class Annotations2 extends CollectionAbstract {
|
|
|
391
395
|
* @returns
|
|
392
396
|
*/
|
|
393
397
|
async findByCanvasUri(queryUrl, canvasUri, asAnnotationList=false) {
|
|
394
|
-
const annotations = await this.find({
|
|
395
|
-
"on.full": canvasUri
|
|
396
|
-
});
|
|
398
|
+
const annotations = await this.find({ "on.full": canvasUri });
|
|
397
399
|
return asAnnotationList
|
|
398
400
|
? toAnnotationList(annotations, queryUrl, `annotations targeting canvas ${canvasUri}`)
|
|
399
401
|
: annotations;
|
|
@@ -65,6 +65,7 @@ function annotationsRoutes(fastify, options, done) {
|
|
|
65
65
|
/** @type {Annotations3InstanceType} */
|
|
66
66
|
annotations3 = fastify.annotations3,
|
|
67
67
|
iiifPresentationVersionSchema = fastify.schemasBase.getSchema("presentation"),
|
|
68
|
+
routeAnnotation2Or3Schema = fastify.schemasRoutes.getSchema("routeAnnotation2Or3"),
|
|
68
69
|
routeAnnotationCreateManySchema = fastify.schemasRoutes.getSchema("routeAnnotationCreateMany"),
|
|
69
70
|
iiifAnnotationListSchema = fastify.schemasPresentation2.getSchema("annotationList"),
|
|
70
71
|
iiifAnnotation2ArraySchema = fastify.schemasPresentation2.getSchema("annotationArray"),
|
|
@@ -169,7 +170,7 @@ function annotationsRoutes(fastify, options, done) {
|
|
|
169
170
|
action: { type: "string", enum: [ "create", "update" ] }
|
|
170
171
|
}
|
|
171
172
|
},
|
|
172
|
-
body:
|
|
173
|
+
body: routeAnnotation2Or3Schema,
|
|
173
174
|
response: responsePostSchema
|
|
174
175
|
},
|
|
175
176
|
},
|
|
@@ -31,7 +31,7 @@ test("test annotation Routes", async (t) => {
|
|
|
31
31
|
annotationListArray,
|
|
32
32
|
annotationListUriInvalid,
|
|
33
33
|
annotationListUriArrayInvalid
|
|
34
|
-
} = fastify.
|
|
34
|
+
} = fastify.fixtures;
|
|
35
35
|
|
|
36
36
|
await fastify.ready();
|
|
37
37
|
// close the app after running the tests
|
|
@@ -71,8 +71,8 @@ test("test annotation Routes", async (t) => {
|
|
|
71
71
|
await t.test("test route /annotations/:iiifPresentationVersion/create", async (t) => {
|
|
72
72
|
//NOTE: we can't do Promise.all because it causes a data race that can cause a failure of unique constraints (i.e., on manifests '@id')
|
|
73
73
|
const data = [
|
|
74
|
-
[fastify.
|
|
75
|
-
[fastify.
|
|
74
|
+
[fastify.fixtures.annotations2Valid, testPostRouteCreateSuccess],
|
|
75
|
+
[fastify.fixtures.annotations2Invalid, testPostRouteCreateFailure],
|
|
76
76
|
]
|
|
77
77
|
for ( let i=0; i<data.length; i++ ) {
|
|
78
78
|
let [ testData, func ] = data.at(i);
|
|
@@ -126,7 +126,7 @@ test("test annotation Routes", async (t) => {
|
|
|
126
126
|
annotation = await getRandomItem(
|
|
127
127
|
await fastify.mongo.db.collection("annotations2").find().toArray()
|
|
128
128
|
),
|
|
129
|
-
canvasId = annotation.on.full,
|
|
129
|
+
canvasId = getRandomItem(annotation.on).full,
|
|
130
130
|
r = await fastify.inject({
|
|
131
131
|
method: "GET",
|
|
132
132
|
url: `/annotations/2/search?uri=${canvasId}&asAnnotationList=${asAnnotationList}`
|
package/src/data/routes.test.js
CHANGED
|
@@ -16,7 +16,7 @@ test("test common routes", async (t) => {
|
|
|
16
16
|
fastify = await build("test"),
|
|
17
17
|
testPostRoute = testPostRouteCurry(fastify),
|
|
18
18
|
testDeleteRoute = testDeleteRouteCurry(fastify),
|
|
19
|
-
{ manifest2Valid, annotationList } = fastify.
|
|
19
|
+
{ manifest2Valid, annotationList } = fastify.fixtures;
|
|
20
20
|
|
|
21
21
|
await fastify.ready();
|
|
22
22
|
// close the app after running the tests
|
|
@@ -88,16 +88,16 @@ test("test common routes", async (t) => {
|
|
|
88
88
|
? deleteBy==="uri"
|
|
89
89
|
? getRandomItem(annotations.map((a) => a["@id"]))
|
|
90
90
|
: deleteBy==="canvasUri"
|
|
91
|
-
? getRandomItem(annotations.map((a) => a.on.full))
|
|
92
|
-
: getRandomItem(annotations.map((a) => a.on.manifestShortId))
|
|
91
|
+
? getRandomItem(annotations.map((a) => getRandomItem(a.on).full))
|
|
92
|
+
: getRandomItem(annotations.map((a) => getRandomItem(a.on).manifestShortId))
|
|
93
93
|
: `invalid-filter-${uuid4()}`,
|
|
94
94
|
expectedDeletedCount =
|
|
95
95
|
validFilter
|
|
96
96
|
? deleteBy==="uri"
|
|
97
97
|
? annotations.filter((a) => a["@id"]===deleteKey).length
|
|
98
98
|
: deleteBy==="canvasUri"
|
|
99
|
-
? annotations.filter((a) => a.on.full===deleteKey).length
|
|
100
|
-
: annotations.filter((a) => a.on.manifestShortId===deleteKey).length
|
|
99
|
+
? annotations.filter((a) => a.on.some((x) => x.full===deleteKey)).length
|
|
100
|
+
: annotations.filter((a) => a.on.some((x) => x.manifestShortId===deleteKey)).length
|
|
101
101
|
: 0;
|
|
102
102
|
|
|
103
103
|
await testDeleteRoute(t, `/annotations/2/delete?${deleteBy}=${deleteKey}`, expectedDeletedCount);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { v4 as uuid4 } from "uuid";
|
|
2
2
|
|
|
3
|
-
import { maybeToArray, getHash, isNullish, isObject } from "#utils/utils.js";
|
|
3
|
+
import { maybeToArray, getHash, isNullish, isObject, objectHasKey, visibleLog } from "#utils/utils.js";
|
|
4
4
|
import { IIIF_PRESENTATION_2, IIIF_PRESENTATION_2_CONTEXT } from "#utils/iiifUtils.js";
|
|
5
5
|
|
|
6
6
|
/** @typedef {import("#types").MongoCollectionType} MongoCollectionType */
|
|
@@ -84,44 +84,52 @@ const getAnnotationTarget = (annotation) => {
|
|
|
84
84
|
/**
|
|
85
85
|
* convert the annotation's `on` to a SpecificResource
|
|
86
86
|
* reimplemented from SAS: https://github.com/glenrobson/SimpleAnnotationServer/blob/dc7c8c6de9f4693c678643db2a996a49eebfcbb0/src/main/java/uk/org/llgc/annotation/store/AnnotationUtils.java#L123-L135
|
|
87
|
+
* @param {object} annotation
|
|
88
|
+
* @returns {object[]} - the array of targets extracted from 'annotation.on'
|
|
87
89
|
*/
|
|
88
90
|
const makeTarget = (annotation) => {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
const err = new Error(`${makeTarget.name}: could not make target for annotation: 'annotation.on' must be an URI, an object or an array of objects containing { on: {@id: "<string URI>", @type: "oa:SpecificResource"} }`, { info: annotation });
|
|
92
|
+
|
|
93
|
+
/** annotation.on is converted to an array => this function creates a target from a single item of that array */
|
|
94
|
+
const makeSingleTarget = (_target) => {
|
|
95
|
+
let specificResource;
|
|
96
|
+
|
|
97
|
+
// convert to SpecificResource if it's not aldready the case
|
|
98
|
+
if ( typeof(_target) === "string" && !isNullish(_target) ) {
|
|
99
|
+
let [full, fragment] = _target.split("#");
|
|
100
|
+
specificResource = {
|
|
101
|
+
"@id": _target,
|
|
102
|
+
"@type": "oa:SpecificResource",
|
|
103
|
+
full: full,
|
|
104
|
+
selector: {
|
|
105
|
+
"@type": "oa:FragmentSelector",
|
|
106
|
+
value: fragment
|
|
107
|
+
}
|
|
106
108
|
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
} else if ( isObject(_target) ) {
|
|
110
|
+
if ( _target["@type"] === "oa:SpecificResource" && !isNullish(_target["full"]) ) {
|
|
111
|
+
specificResource = _target;
|
|
112
|
+
// the received specificResource `selector` may have its type specified using the key `type`. correct it to `@type`.
|
|
113
|
+
if ( specificResource.selector !== undefined && isObject(specificResource.selector) && Object.keys(specificResource.selector).includes("type") ) {
|
|
114
|
+
specificResource.selector["@type"] = specificResource.selector.type;
|
|
115
|
+
delete specificResource.selector.type;
|
|
116
|
+
}
|
|
117
|
+
// if '_target' is an object but not a specificresource, raise.
|
|
118
|
+
} else {
|
|
119
|
+
throw err
|
|
116
120
|
}
|
|
121
|
+
// if _target is neither a string nor an object, raise
|
|
117
122
|
} else {
|
|
118
123
|
throw err
|
|
119
124
|
}
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
if ( objectHasKey(specificResource, "full") ) {
|
|
126
|
+
specificResource.manifestShortId = getManifestShortId(specificResource.full);
|
|
127
|
+
specificResource.manifestUri = manifestUri(specificResource.manifestShortId);
|
|
128
|
+
}
|
|
129
|
+
return specificResource
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
return
|
|
132
|
+
return maybeToArray(annotation.on).map(makeSingleTarget);
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
/**
|
|
@@ -12,7 +12,7 @@ const
|
|
|
12
12
|
test("test 'iiif2Utils' functions", async (t) => {
|
|
13
13
|
const
|
|
14
14
|
fastify = await build("test"),
|
|
15
|
-
{ annotations2Valid, annotations2Invalid } = fastify.
|
|
15
|
+
{ annotations2Valid, annotations2Invalid } = fastify.fixtures;
|
|
16
16
|
|
|
17
17
|
await fastify.ready();
|
|
18
18
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// test data for the annotations create and createMany functions.
|
|
2
|
-
import { readFileToObject, toUrl } from "#
|
|
2
|
+
import { readFileToObject, toUrl } from "#fixtures/utils.js";
|
|
3
3
|
|
|
4
4
|
const annotations2Invalid = readFileToObject("annotations2Invalid.jsonld");
|
|
5
5
|
const annotations2Valid = readFileToObject("annotations2Valid.jsonld");
|
|
@@ -14,10 +14,10 @@ const annotationListUriArray = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
// will trigger an error because the path doesn't exist
|
|
17
|
-
const annotationListUriInvalid = { uri: "/
|
|
17
|
+
const annotationListUriInvalid = { uri: "/fixtures/annotationList_that_does_not_exist.jsonld" };
|
|
18
18
|
|
|
19
19
|
const annotationListUriArrayInvalid = [
|
|
20
|
-
{ uri: "/
|
|
20
|
+
{ uri: "/fixtures/annotationList_that_does_not_exist.jsonld" }
|
|
21
21
|
];
|
|
22
22
|
|
|
23
23
|
const annotationList = readFileToObject("annotationList_aikon_wit9_man11_anno165_all.jsonld");
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* the `
|
|
2
|
+
* the `fixtures` plugin makes test files available to the entire fastify app, mostly for testing purposes.
|
|
3
3
|
*/
|
|
4
4
|
import fastifyPlugin from "fastify-plugin";
|
|
5
5
|
|
|
6
|
-
import { annotations2Invalid, annotations2Valid, annotationListUri, annotationListUriArray, annotationList, annotationListArray, annotationListUriInvalid, annotationListUriArrayInvalid } from "#src/
|
|
7
|
-
import { manifest2Valid, manifest2ValidUri, manifest2Invalid, manifest2InvalidUri } from "#
|
|
8
|
-
import { readFileToObject } from "#
|
|
6
|
+
import { annotations2Invalid, annotations2Valid, annotationListUri, annotationListUriArray, annotationList, annotationListArray, annotationListUriInvalid, annotationListUriArrayInvalid } from "#src/fixtures/annotations.js";
|
|
7
|
+
import { manifest2Valid, manifest2ValidUri, manifest2Invalid, manifest2InvalidUri } from "#fixtures/manifests.js";
|
|
8
|
+
import { readFileToObject } from "#fixtures/utils.js";
|
|
9
9
|
|
|
10
10
|
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
11
11
|
|
|
@@ -15,11 +15,11 @@ import { readFileToObject } from "#fileServer/utils.js";
|
|
|
15
15
|
* @param {FastifyInstanceType} fastify Encapsulated Fastify Instance
|
|
16
16
|
* @param {object} options
|
|
17
17
|
*/
|
|
18
|
-
async function
|
|
18
|
+
async function fixtures(fastify, options) {
|
|
19
19
|
|
|
20
20
|
/** route to return a file in `dataDir` */
|
|
21
21
|
fastify.get(
|
|
22
|
-
"/
|
|
22
|
+
"/fixtures/:fileName",
|
|
23
23
|
{
|
|
24
24
|
schema: {
|
|
25
25
|
params: {
|
|
@@ -45,7 +45,7 @@ async function fileServer(fastify, options) {
|
|
|
45
45
|
}
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
-
fastify.decorate("
|
|
48
|
+
fastify.decorate("fixtures", {
|
|
49
49
|
annotationListUri: annotationListUri,
|
|
50
50
|
annotationListUriArray: annotationListUriArray,
|
|
51
51
|
annotationList: annotationList,
|
|
@@ -61,4 +61,4 @@ async function fileServer(fastify, options) {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export default fastifyPlugin(
|
|
64
|
+
export default fastifyPlugin(fixtures);
|
|
@@ -4,13 +4,13 @@ import fs from "node:fs";
|
|
|
4
4
|
|
|
5
5
|
// path to dirctory of curent file
|
|
6
6
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
7
|
-
// path to
|
|
7
|
+
// path to fixtures/data
|
|
8
8
|
const dataDir = path.join(__dirname, "data");
|
|
9
9
|
|
|
10
10
|
const availableFiles = fs.readdirSync(dataDir);
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* for simplicity, `readFileToObject` is synchronous. given that `
|
|
13
|
+
* for simplicity, `readFileToObject` is synchronous. given that `fixtures` should only be used in tests, there is no performance downgrade for the prod server.
|
|
14
14
|
* @param {string} fn: the filename
|
|
15
15
|
* @returns {object}
|
|
16
16
|
*/
|
|
@@ -25,7 +25,7 @@ const readFileToObject = (fn) => {
|
|
|
25
25
|
* @param {string} fn
|
|
26
26
|
* @returns {string}
|
|
27
27
|
*/
|
|
28
|
-
const toUrl = (fn) => `${process.env.AIIINOTATE_BASE_URL}/
|
|
28
|
+
const toUrl = (fn) => `${process.env.AIIINOTATE_BASE_URL}/fixtures/${fn}`;
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
export {
|
|
@@ -201,12 +201,15 @@ function addSchemas(fastify, options, done) {
|
|
|
201
201
|
|
|
202
202
|
fastify.addSchema({
|
|
203
203
|
$id: makeSchemaUri("annotationTarget"),
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
204
|
+
type: "array",
|
|
205
|
+
items: {
|
|
206
|
+
anyOf: [
|
|
207
|
+
// URI
|
|
208
|
+
{ type: "string" },
|
|
209
|
+
// SpecificResource
|
|
210
|
+
{ $ref: makeSchemaUri("specificResource") }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
210
213
|
})
|
|
211
214
|
|
|
212
215
|
fastify.addSchema({
|
|
@@ -41,7 +41,20 @@ function addSchemas(fastify, options, done) {
|
|
|
41
41
|
{ type: "string", enum: [ "oa:Annotation" ] },
|
|
42
42
|
{ type: "array", items: { type: "string" }}
|
|
43
43
|
]},
|
|
44
|
-
on: {
|
|
44
|
+
on: {
|
|
45
|
+
anyOf: [
|
|
46
|
+
{ type: "string" },
|
|
47
|
+
{ type: "object" },
|
|
48
|
+
{
|
|
49
|
+
type: "array",
|
|
50
|
+
items: [
|
|
51
|
+
{ type: "string" },
|
|
52
|
+
{ type: "object" }
|
|
53
|
+
],
|
|
54
|
+
minItems: 1
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
45
58
|
}
|
|
46
59
|
});
|
|
47
60
|
|
|
@@ -56,7 +69,20 @@ function addSchemas(fastify, options, done) {
|
|
|
56
69
|
{ type: "string" },
|
|
57
70
|
{ type: "array", items: { type: "string" } },
|
|
58
71
|
]},
|
|
59
|
-
target: {
|
|
72
|
+
target: {
|
|
73
|
+
anyOf: [
|
|
74
|
+
{ type: "string" },
|
|
75
|
+
{ type: "object" },
|
|
76
|
+
{
|
|
77
|
+
type: "array",
|
|
78
|
+
items: [
|
|
79
|
+
{ type: "string" },
|
|
80
|
+
{ type: "object" }
|
|
81
|
+
],
|
|
82
|
+
minItems: 1
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
60
86
|
}
|
|
61
87
|
})
|
|
62
88
|
|
/package/src/{fileServer → fixtures}/data/annotationList_aikon_wit9_man11_anno165_all.jsonld
RENAMED
|
File without changes
|
/package/src/{fileServer → fixtures}/data/annotationList_vhs_wit250_man250_anno250_all.jsonld
RENAMED
|
File without changes
|
/package/src/{fileServer → fixtures}/data/annotationList_vhs_wit253_man253_anno253_all.jsonld
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|