aiiinotate 0.2.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/LICENSE +661 -0
- package/README.md +61 -0
- package/cli/import.js +142 -0
- package/cli/index.js +26 -0
- package/cli/io.js +105 -0
- package/cli/migrate.js +123 -0
- package/cli/mongoClient.js +11 -0
- package/docs/architecture.md +88 -0
- package/docs/db.md +38 -0
- package/docs/dev_iiif_compatibility.md +43 -0
- package/docs/endpoints.md +48 -0
- package/docs/progress.md +159 -0
- package/docs/specifications/0_w3c_open_annotations.md +332 -0
- package/docs/specifications/1_w3c_web_annotations.md +577 -0
- package/docs/specifications/2_iiif_apis.md +396 -0
- package/docs/specifications/3_iiif_annotations.md +103 -0
- package/docs/specifications/4_search_api.md +135 -0
- package/docs/specifications/5_sas.md +119 -0
- package/docs/specifications/6_mirador.md +119 -0
- package/docs/specifications/7_aikon.md +137 -0
- package/docs/specifications/include/presentation_2.0.webp +0 -0
- package/docs/specifications/include/presentation_2.0_white.png +0 -0
- package/docs/specifications/include/presentation_3.0.png +0 -0
- package/docs/specifications/include/presentation_3.0_resize.png +0 -0
- package/eslint.config.js +27 -0
- package/migrations/baseConfig.js +56 -0
- package/migrations/manageIndex.js +55 -0
- package/migrations/migrate-mongo-config-main.js +8 -0
- package/migrations/migrate-mongo-config-test.js +8 -0
- package/migrations/migrationScripts/20250825185706-collections.js +41 -0
- package/migrations/migrationScripts/20250826194832-annotations2-canvas-index.js +31 -0
- package/migrations/migrationScripts/20250904080710-annotations2-schema.js +42 -0
- package/migrations/migrationScripts/20251002141951-manifest2-schema.js +43 -0
- package/migrations/migrationScripts/20251006212110-manifest-unique-index.js +29 -0
- package/migrations/migrationScripts/20251028115614-annotations2-id-index.js +27 -0
- package/migrations/migrationTemplate.js +25 -0
- package/package.json +78 -0
- package/run.sh +70 -0
- package/scripts/_migrations.sh +79 -0
- package/scripts/_setup.js +31 -0
- package/scripts/setup_mongodb.sh +61 -0
- package/scripts/setup_mongodb_migrate.sh +17 -0
- package/scripts/setup_node.sh +15 -0
- package/scripts/utils.sh +192 -0
- package/setup.sh +20 -0
- package/src/app.js +113 -0
- package/src/config/.env.template +22 -0
- package/src/data/annotations/annotations2.js +419 -0
- package/src/data/annotations/annotations3.js +32 -0
- package/src/data/annotations/routes.js +271 -0
- package/src/data/annotations/routes.test.js +180 -0
- package/src/data/collectionAbstract.js +270 -0
- package/src/data/index.js +29 -0
- package/src/data/manifests/manifests2.js +305 -0
- package/src/data/manifests/manifests2.test.js +53 -0
- package/src/data/manifests/manifests3.js +23 -0
- package/src/data/manifests/routes.js +95 -0
- package/src/data/manifests/routes.test.js +69 -0
- package/src/data/routes.js +141 -0
- package/src/data/routes.test.js +117 -0
- package/src/data/utils/iiif2Utils.js +196 -0
- package/src/data/utils/iiif2Utils.test.js +98 -0
- package/src/data/utils/iiif3Utils.js +0 -0
- package/src/data/utils/iiifUtils.js +18 -0
- package/src/data/utils/routeUtils.js +109 -0
- package/src/data/utils/testUtils.js +253 -0
- package/src/data/utils/utils.js +231 -0
- package/src/db/index.js +48 -0
- package/src/fileServer/annotations.js +39 -0
- package/src/fileServer/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
- package/src/fileServer/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
- package/src/fileServer/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
- package/src/fileServer/data/annotations2Invalid.jsonld +39 -0
- package/src/fileServer/data/annotations2Valid.jsonld +39 -0
- package/src/fileServer/data/bnf_invalid_manifest.json +2806 -0
- package/src/fileServer/data/bnf_valid_manifest.json +2817 -0
- package/src/fileServer/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
- package/src/fileServer/index.js +64 -0
- package/src/fileServer/manifests.js +14 -0
- package/src/fileServer/utils.js +35 -0
- package/src/schemas/index.js +20 -0
- package/src/schemas/schemasBase.js +47 -0
- package/src/schemas/schemasPresentation2.js +417 -0
- package/src/schemas/schemasPresentation3.js +57 -0
- package/src/schemas/schemasResolver.js +71 -0
- package/src/schemas/schemasRoutes.js +277 -0
- package/src/server.js +22 -0
- package/src/types.js +93 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import fastifyPlugin from "fastify-plugin"
|
|
2
|
+
|
|
3
|
+
import { pathToUrl, objectHasKey, maybeToArray, inspectObj, throwIfKeyUndefined, throwIfValueError, getFirstNonEmptyPair, visibleLog } from "#utils/utils.js";
|
|
4
|
+
import { makeResponseSchema, makeResponsePostSchema, returnError } from "#utils/routeUtils.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
8
|
+
/** @typedef {import("#types").Annotations2InstanceType} Annotations2InstanceType */
|
|
9
|
+
/** @typedef {import("#types").Annotations3InstanceType} Annotations3InstanceType */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* validate an annotation, annotationPage or annotationList: that is, ensure it fits the IIIF presentation API
|
|
13
|
+
* @param {2|3} iiifPresentationVersion
|
|
14
|
+
* @param {object} annotationData
|
|
15
|
+
* @param {boolean} isListOrPage: it's an annotationList or annotationPage instead of a regular annotation.
|
|
16
|
+
*/
|
|
17
|
+
const validateAnnotationVersion = (iiifPresentationVersion, annotationData, isListOrPage=false) => {
|
|
18
|
+
// object keys are always strings, so we need to convert to string (https://stackoverflow.com/questions/3633362)
|
|
19
|
+
iiifPresentationVersion = iiifPresentationVersion.toString();
|
|
20
|
+
const expectedTypeKeys = {
|
|
21
|
+
"2": "@type",
|
|
22
|
+
"3": "type"
|
|
23
|
+
};
|
|
24
|
+
throwIfKeyUndefined(expectedTypeKeys, iiifPresentationVersion);
|
|
25
|
+
const expectedTypeKey = expectedTypeKeys[iiifPresentationVersion];
|
|
26
|
+
throwIfKeyUndefined(annotationData, expectedTypeKey);
|
|
27
|
+
const expectedTypeVal = (
|
|
28
|
+
isListOrPage
|
|
29
|
+
? { "2": "sc:AnnotationList", "3":"AnnotationPage"}
|
|
30
|
+
: { "2": "oa:Annotation", "3": "Annotation" }
|
|
31
|
+
)[iiifPresentationVersion];
|
|
32
|
+
throwIfValueError(annotationData, expectedTypeKey, expectedTypeVal);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `annotationArray` is an array of AnnotationLists or AnnotationPages, depending on the IIIF Presentaion API version.
|
|
37
|
+
* assert that it indeed the case, raise otherwise
|
|
38
|
+
* @param {2|3} iiifPresentationVersion
|
|
39
|
+
* @param {object[]} annotationArray
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*/
|
|
42
|
+
const validateAnnotationArrayVersion = (iiifPresentationVersion, annotationArray) =>
|
|
43
|
+
annotationArray.map(annotationData => validateAnnotationVersion(iiifPresentationVersion, annotationData, true));
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {import("#types").InsertResponseArrayType} insertResponseArray
|
|
47
|
+
* @returns {import("#types").InsertResponseType}
|
|
48
|
+
*/
|
|
49
|
+
const reduceInsertResponseArray = (insertResponseArray) => ({
|
|
50
|
+
insertedCount: insertResponseArray.reduce((acc, r) => acc+r.insertedCount, 0),
|
|
51
|
+
insertedIds: insertResponseArray.reduce((acc, r) => acc.concat(r.insertedIds), [])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Encapsulates the routes
|
|
57
|
+
* @param {FastifyInstanceType} fastify Encapsulated Fastify Instance
|
|
58
|
+
* @param {object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options
|
|
59
|
+
* @param {Function} done
|
|
60
|
+
*/
|
|
61
|
+
function annotationsRoutes(fastify, options, done) {
|
|
62
|
+
const
|
|
63
|
+
/** @type {Annotations2InstanceType} */
|
|
64
|
+
annotations2 = fastify.annotations2,
|
|
65
|
+
/** @type {Annotations3InstanceType} */
|
|
66
|
+
annotations3 = fastify.annotations3,
|
|
67
|
+
iiifPresentationVersionSchema = fastify.schemasBase.getSchema("presentation"),
|
|
68
|
+
routeAnnotationCreateManySchema = fastify.schemasRoutes.getSchema("routeAnnotationCreateMany"),
|
|
69
|
+
iiifAnnotationListSchema = fastify.schemasPresentation2.getSchema("annotationList"),
|
|
70
|
+
iiifAnnotation2ArraySchema = fastify.schemasPresentation2.getSchema("annotationArray"),
|
|
71
|
+
iiifAnnotation2Schema = fastify.schemasPresentation2.getSchema("annotation"),
|
|
72
|
+
responsePostSchema = makeResponsePostSchema(fastify);
|
|
73
|
+
|
|
74
|
+
/////////////////////////////////////////////////////////
|
|
75
|
+
// get routes
|
|
76
|
+
|
|
77
|
+
/** get all annotations by a canvas URI */
|
|
78
|
+
fastify.get(
|
|
79
|
+
"/annotations/:iiifPresentationVersion/search",
|
|
80
|
+
{
|
|
81
|
+
schema: {
|
|
82
|
+
params: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
iiifPresentationVersion: iiifPresentationVersionSchema
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
querystring: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
uri: { type: "string" },
|
|
92
|
+
asAnnotationList: { type: "boolean" },
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
response: makeResponseSchema(
|
|
96
|
+
fastify,
|
|
97
|
+
{
|
|
98
|
+
oneOf: [
|
|
99
|
+
fastify.schemasResolver(iiifAnnotationListSchema),
|
|
100
|
+
fastify.schemasResolver(iiifAnnotation2ArraySchema)
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
async (request, reply) => {
|
|
107
|
+
const
|
|
108
|
+
queryUrl = pathToUrl(request.url),
|
|
109
|
+
{ iiifPresentationVersion } = request.params,
|
|
110
|
+
{ uri, asAnnotationList } = request.query;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
if (iiifPresentationVersion === 2) {
|
|
114
|
+
return await annotations2.findByCanvasUri(queryUrl, uri, asAnnotationList);
|
|
115
|
+
} else {
|
|
116
|
+
annotations3.notImplementedError();
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
returnError(request, reply, err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
/** retrieve a single annotation by its "@id"|"id". this route defers an annotation */
|
|
125
|
+
fastify.get(
|
|
126
|
+
"/data/:iiifPresentationVersion/:manifestShortId/annotation/:annotationShortId",
|
|
127
|
+
{
|
|
128
|
+
schema: {
|
|
129
|
+
params: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
iiifPresentationVersion: iiifPresentationVersionSchema,
|
|
133
|
+
manifestShortId: { type: "string" },
|
|
134
|
+
annotationShortId: { type: "string" },
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
response: makeResponseSchema(
|
|
138
|
+
fastify,
|
|
139
|
+
fastify.schemasResolver(iiifAnnotation2Schema)
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
async (request, reply) => {
|
|
144
|
+
const
|
|
145
|
+
annotationUri = pathToUrl(request.url),
|
|
146
|
+
{ iiifPresentationVersion} = request.params;
|
|
147
|
+
try {
|
|
148
|
+
return iiifPresentationVersion === 2
|
|
149
|
+
? annotations2.findById(annotationUri)
|
|
150
|
+
: annotations3.notImplementedError();
|
|
151
|
+
} catch (err) {
|
|
152
|
+
returnError(request, reply, err);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
/////////////////////////////////////////////////////////
|
|
158
|
+
// create/update routes
|
|
159
|
+
|
|
160
|
+
/** create or update a single annotation from an annotation object */
|
|
161
|
+
fastify.post(
|
|
162
|
+
"/annotations/:iiifPresentationVersion/:action",
|
|
163
|
+
{
|
|
164
|
+
schema: {
|
|
165
|
+
params: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: {
|
|
168
|
+
iiifPresentationVersion: iiifPresentationVersionSchema,
|
|
169
|
+
action: { type: "string", enum: [ "create", "update" ] }
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
body: { type: "object" } /* routeAnnotations2Or3Schema */,
|
|
173
|
+
response: responsePostSchema
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
async (request, reply) => {
|
|
177
|
+
const
|
|
178
|
+
{ iiifPresentationVersion, action } = request.params,
|
|
179
|
+
annotation = request.body;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
validateAnnotationVersion(iiifPresentationVersion, annotation);
|
|
183
|
+
// insert or update
|
|
184
|
+
if ( iiifPresentationVersion === 2 ) {
|
|
185
|
+
return action==="create"
|
|
186
|
+
? await annotations2.insertAnnotation(annotation)
|
|
187
|
+
: await annotations2.updateAnnotation(annotation);
|
|
188
|
+
} else {
|
|
189
|
+
annotations3.notImplementedError();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
} catch (err) {
|
|
193
|
+
returnError(request, reply, err, request.body);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* create several annotations from:
|
|
200
|
+
* - an annotationList (if iiifPresentationVersion === 2)
|
|
201
|
+
* - an annotationPage (if iiifPresentationVersion === 3)
|
|
202
|
+
* - an URI to an annotationList or annotationPage
|
|
203
|
+
* - or an Array of any of the previous
|
|
204
|
+
*
|
|
205
|
+
* NOTE that POST body size is limited to 1MB, so your query might be rejected. body size is limited by:
|
|
206
|
+
* - fastify's `bodyLimit` (https://fastify.dev/docs/latest/Reference/Server/#bodylimit)
|
|
207
|
+
* - nginx's `client_max_body_size` (https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size)
|
|
208
|
+
* - any other server's max body size.
|
|
209
|
+
* both fastify and nginx limit body size defaults to to 1MB.
|
|
210
|
+
*/
|
|
211
|
+
fastify.post(
|
|
212
|
+
"/annotations/:iiifPresentationVersion/createMany",
|
|
213
|
+
{
|
|
214
|
+
schema: {
|
|
215
|
+
params: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
iiifPresentationVersion: iiifPresentationVersionSchema
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
body: routeAnnotationCreateManySchema,
|
|
222
|
+
response: responsePostSchema
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
async (request, reply) => {
|
|
226
|
+
const
|
|
227
|
+
{ iiifPresentationVersion } = request.params,
|
|
228
|
+
body = maybeToArray(request.body), // convert to an array to have a homogeneous data structure
|
|
229
|
+
insertResponseArray = [];
|
|
230
|
+
|
|
231
|
+
// data to actually insert (body with resolved URIs, if the body is `annotationListOrPageUri` or `annotationListOrPageUriArray`)
|
|
232
|
+
let annotationsArray = [];
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
// if we received `annotationListOrPageUri` or `annotationListOrPageUriArray`, fetch objects
|
|
236
|
+
const asUri = body.find(item => objectHasKey(item, "uri")) !== undefined;
|
|
237
|
+
if (asUri) {
|
|
238
|
+
annotationsArray = await Promise.all(
|
|
239
|
+
body.map(async (item) =>
|
|
240
|
+
fetch(item.uri).then(r => r.json()))
|
|
241
|
+
);
|
|
242
|
+
} else {
|
|
243
|
+
annotationsArray = body;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
validateAnnotationArrayVersion(iiifPresentationVersion, annotationsArray);
|
|
247
|
+
|
|
248
|
+
// insert
|
|
249
|
+
if ( iiifPresentationVersion === 2 ) {
|
|
250
|
+
await Promise.all(annotationsArray.map(
|
|
251
|
+
async (annotationList) => {
|
|
252
|
+
const r = await annotations2.insertAnnotationList(annotationList);
|
|
253
|
+
insertResponseArray.push(r);
|
|
254
|
+
}
|
|
255
|
+
));
|
|
256
|
+
return reduceInsertResponseArray(insertResponseArray);
|
|
257
|
+
} else {
|
|
258
|
+
annotations3.notImplementedError();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
} catch (err) {
|
|
262
|
+
returnError(request, reply, err, request.body);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
done();
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export default fastifyPlugin(annotationsRoutes);
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
|
|
3
|
+
import build from "#src/app.js";
|
|
4
|
+
|
|
5
|
+
import { v4 as uuid4 } from "uuid";
|
|
6
|
+
|
|
7
|
+
import { inspectObj, isObject, getRandomItem, visibleLog } from "#utils/utils.js"
|
|
8
|
+
import { testPostRouteCurry, testDeleteRouteCurry, injectTestAnnotations } from "#utils/testUtils.js";
|
|
9
|
+
|
|
10
|
+
/** @typedef {import("#types").NodeTestType} NodeTestType */
|
|
11
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
12
|
+
/** @typedef {import("#types").FastifyReplyType} FastifyReplyType */
|
|
13
|
+
/** @typedef {import("#types").DataOperationsType} DataOperationsType */
|
|
14
|
+
|
|
15
|
+
test("test annotation Routes", async (t) => {
|
|
16
|
+
|
|
17
|
+
const
|
|
18
|
+
fastify = await build("test"),
|
|
19
|
+
testPostRoute = testPostRouteCurry(fastify),
|
|
20
|
+
testPostRouteCreate = testPostRoute("insert"),
|
|
21
|
+
testPostRouteUpdate = testPostRoute("update"),
|
|
22
|
+
testDeleteRoute = testDeleteRouteCurry(fastify),
|
|
23
|
+
testPostRouteCreateSuccess = testPostRouteCreate(true),
|
|
24
|
+
testPostRouteCreateFailure = testPostRouteCreate(false),
|
|
25
|
+
testPostRouteUpdateSuccess = testPostRouteUpdate(true),
|
|
26
|
+
testPostRouteUpdateFailure = testPostRouteUpdate(false),
|
|
27
|
+
{
|
|
28
|
+
annotationListUri,
|
|
29
|
+
annotationListUriArray,
|
|
30
|
+
annotationList,
|
|
31
|
+
annotationListArray,
|
|
32
|
+
annotationListUriInvalid,
|
|
33
|
+
annotationListUriArrayInvalid
|
|
34
|
+
} = fastify.fileServer;
|
|
35
|
+
|
|
36
|
+
await fastify.ready();
|
|
37
|
+
// close the app after running the tests
|
|
38
|
+
t.after(async () => await fastify.close());
|
|
39
|
+
// after each subtest has run, delete all database records
|
|
40
|
+
t.afterEach(async() => fastify.emptyCollections());
|
|
41
|
+
|
|
42
|
+
// NOTE: it is necessary to run the app because internally there are fetches to external data.
|
|
43
|
+
try {
|
|
44
|
+
await fastify.listen({ port: process.env.APP_PORT });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.log("FASTIFY ERROR", err);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await t.test("test route /annotations/:iiifPresentationVersion/createMany", async (t) => {
|
|
51
|
+
// truncate the contents of `annotationListArray` to avoid an `fst_err_ctp_body_too_large` error
|
|
52
|
+
// `https://fastify.dev/docs/latest/Reference/Errors/#fst_err_ctp_body_too_large`
|
|
53
|
+
const annotationListArrayLimit = annotationListArray.map(a => {
|
|
54
|
+
a.resources = a.resources.length > 500 ? a.resources.slice(0,500) : a.resources
|
|
55
|
+
return a;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
//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')
|
|
59
|
+
const data = [
|
|
60
|
+
[[ annotationListUri, annotationListUriArray, annotationList, annotationListArrayLimit ], testPostRouteCreateSuccess],
|
|
61
|
+
[[ annotationListUriInvalid, annotationListUriArrayInvalid ], testPostRouteCreateFailure]
|
|
62
|
+
];
|
|
63
|
+
for ( let i=0; i<data.length; i++ ) {
|
|
64
|
+
let [ testData, func ] = data.at(i);
|
|
65
|
+
for ( let i=0; i<testData.length; i++ ) {
|
|
66
|
+
await func(t, "/annotations/2/createMany", testData.at(i));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await t.test("test route /annotations/:iiifPresentationVersion/create", async (t) => {
|
|
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
|
+
const data = [
|
|
74
|
+
[fastify.fileServer.annotations2Valid, testPostRouteCreateSuccess],
|
|
75
|
+
[fastify.fileServer.annotations2Invalid, testPostRouteCreateFailure],
|
|
76
|
+
]
|
|
77
|
+
for ( let i=0; i<data.length; i++ ) {
|
|
78
|
+
let [ testData, func ] = data.at(i);
|
|
79
|
+
for ( let i=0; i<testData.length; i++ ) {
|
|
80
|
+
await func(t, "/annotations/2/create", testData.at(i));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
await t.test("test route /annotations/:iiifPresentationVersion/update", async (t) => {
|
|
86
|
+
const updatePipeline = async (annotation, success) => {
|
|
87
|
+
// update the annotation
|
|
88
|
+
const
|
|
89
|
+
newLabel = `label-${uuid4()}`,
|
|
90
|
+
newBody = {
|
|
91
|
+
"@type": "cnt:ContentAsText",
|
|
92
|
+
format: "text/html",
|
|
93
|
+
value: "<p>What a grand pleasure it is to have updated this annotation !</p>"
|
|
94
|
+
};
|
|
95
|
+
annotation.label = newLabel;
|
|
96
|
+
annotation.resource = newBody;
|
|
97
|
+
if (!success) {
|
|
98
|
+
annotation["@type"] = "invalidType";
|
|
99
|
+
}
|
|
100
|
+
success
|
|
101
|
+
? await testPostRouteUpdateSuccess(t, "/annotations/2/update", annotation)
|
|
102
|
+
: await testPostRouteUpdateFailure(t, "/annotations/2/update", annotation);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// insert valid documents and retrieve an annotation to update.
|
|
106
|
+
const
|
|
107
|
+
[ insertedCount, insertedIds ] = await injectTestAnnotations(fastify, t, annotationList),
|
|
108
|
+
idToUpdate = getRandomItem(insertedIds), // get a random item
|
|
109
|
+
annotation = await fastify.mongo.db.collection("annotations2").findOne(
|
|
110
|
+
{ "@id": idToUpdate },
|
|
111
|
+
{ projection: { _id: 0 }}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
await updatePipeline(annotation, true);
|
|
115
|
+
await updatePipeline(annotation, false);
|
|
116
|
+
return;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await t.test("test route /annotations/:iiifPresentationVersion/search", async (t) => {
|
|
120
|
+
await injectTestAnnotations(fastify, t, annotationList);
|
|
121
|
+
await Promise.all(
|
|
122
|
+
// `asAnnotationList` is a boolean defining if we should return an array or an annotationList.
|
|
123
|
+
[false, true].map(async (asAnnotationList) => {
|
|
124
|
+
|
|
125
|
+
const
|
|
126
|
+
annotation = await getRandomItem(
|
|
127
|
+
await fastify.mongo.db.collection("annotations2").find().toArray()
|
|
128
|
+
),
|
|
129
|
+
canvasId = annotation.on.full,
|
|
130
|
+
r = await fastify.inject({
|
|
131
|
+
method: "GET",
|
|
132
|
+
url: `/annotations/2/search?uri=${canvasId}&asAnnotationList=${asAnnotationList}`
|
|
133
|
+
}),
|
|
134
|
+
body = await r.json();
|
|
135
|
+
|
|
136
|
+
t.assert.deepStrictEqual(r.statusCode, 200);
|
|
137
|
+
if ( asAnnotationList ) {
|
|
138
|
+
// we have aldready defined responses for both cases of `asAnnotationList`, so we just need to check that the response is of a proper type
|
|
139
|
+
t.assert.deepStrictEqual(Array.isArray(body), false);
|
|
140
|
+
} else {
|
|
141
|
+
t.assert.deepStrictEqual(Array.isArray(body), true)
|
|
142
|
+
t.assert.deepStrictEqual(body.length > 0, true);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
})
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await t.test("test route /data/:iiifPresentationVersion/:manifestShortId/annotation/:annotationShortId", async (t) => {
|
|
151
|
+
await injectTestAnnotations(fastify, t, annotationList);
|
|
152
|
+
const annotationId = await getRandomItem(
|
|
153
|
+
await fastify.mongo.db.collection("annotations2").find().toArray()
|
|
154
|
+
)["@id"];
|
|
155
|
+
await Promise.all(
|
|
156
|
+
// if shouldExist, search an annotation that exists, otherwise, search an annotation that does not exist. test accordingly.
|
|
157
|
+
[true, false].map(async (shouldExist) => {
|
|
158
|
+
const
|
|
159
|
+
annotationIdQuery =
|
|
160
|
+
shouldExist
|
|
161
|
+
? annotationId.replace(process.env.APP_BASE_URL, "")
|
|
162
|
+
: annotationId.replace(process.env.APP_BASE_URL, "") + "string_that_does_not_exist_in_the_db",
|
|
163
|
+
r = await fastify.inject({
|
|
164
|
+
method: "GET",
|
|
165
|
+
url: annotationIdQuery
|
|
166
|
+
}),
|
|
167
|
+
body = await r.json();
|
|
168
|
+
|
|
169
|
+
t.assert.deepStrictEqual(r.statusCode, 200);
|
|
170
|
+
if ( shouldExist ) {
|
|
171
|
+
t.assert.deepStrictEqual(body["@id"]===annotationId, true);
|
|
172
|
+
} else {
|
|
173
|
+
t.assert.deepStrictEqual(Object.keys(body).length === 0, true);
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return
|
|
180
|
+
})
|