aiiinotate 0.4.2 → 0.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiiinotate",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "a fast IIIF-compliant annotation server",
5
5
  "main": "./cli/index.js",
6
6
  "type": "module",
@@ -346,7 +346,8 @@ class Annotations2 extends CollectionAbstract {
346
346
  }
347
347
 
348
348
  /**
349
- * implementation of the IIIF Search API 1.0
349
+ * implementation of the IIIF Search API 1.0.
350
+ * function arguments have been validated by JSONSchemas at route-level so they're clean.
350
351
  *
351
352
  * NOTE:
352
353
  * - only `motivation` and `q` search params are implemented
@@ -354,6 +355,7 @@ class Annotations2 extends CollectionAbstract {
354
355
  * implemented for `q` and `motivation` (in the IIIF specs, you can supply
355
356
  * multiple space-separated values and the server should return all partial
356
357
  * matches to any of those strings.)
358
+ * - non-standard `canvasMin` and `canvasMax` parameters are implemented: search by annotation's target canvas position.
357
359
  *
358
360
  * see:
359
361
  * https://iiif.io/api/search/1.0/
@@ -361,11 +363,13 @@ class Annotations2 extends CollectionAbstract {
361
363
  *
362
364
  * @param {string} queryUrl
363
365
  * @param {string} manifestShortId
364
- * @param {string} q
365
- * @param {"painting"|"non-painting"|"commenting"|"describing"|"tagging"|"linking"} motivation
366
+ * @param {string?} q
367
+ * @param {"painting"|"non-painting"|"commenting"|"describing"|"tagging"|"linking"?} motivation
368
+ * @param {number?} canvasMin - minimum value of `on.canvasIdx`, inclusive
369
+ * @param {number?} canvasMax - maximum value of `on.canvasIdx`, inclusive
366
370
  * @returns {object} annotationList containing results
367
371
  */
368
- async search(queryUrl, manifestShortId, q, motivation) {
372
+ async search(queryUrl, manifestShortId, q, motivation, canvasMin, canvasMax) {
369
373
  const
370
374
  queryBase = { "on.manifestShortId": manifestShortId },
371
375
  queryFilters = { $and: [] };
@@ -379,7 +383,7 @@ class Annotations2 extends CollectionAbstract {
379
383
  { "resource.chars": q }
380
384
  ]
381
385
  });
382
- }
386
+ };
383
387
  if ( motivation ) {
384
388
  queryFilters.$and.push(
385
389
  motivation === "non-painting"
@@ -388,6 +392,23 @@ class Annotations2 extends CollectionAbstract {
388
392
  ? { motivation: "sc:painting" }
389
393
  : { motivation: `oa:${motivation}` }
390
394
  );
395
+ };
396
+ // TODO test
397
+ if ( canvasMin ) {
398
+ // if canvasMax is undefined, then search for canvasIdx===canvasMin
399
+ if ( !canvasMax ) {
400
+ queryFilters.$and.push({ "on.canvasIdx": canvasMin })
401
+ // if canvasMin and canvasMax, canvasIdx must be in [canvasMin, canvasMax] (inclusive).
402
+ } else {
403
+ queryFilters.$and.push({
404
+ "on.canvasIdx": {
405
+ $and: [
406
+ { $gte: canvasMin },
407
+ { $lte: canvasMax },
408
+ ]
409
+ }
410
+ })
411
+ }
391
412
  }
392
413
  const query =
393
414
  queryFilters.$and.length
@@ -4,12 +4,12 @@ import CollectionAbstract from "#data/collectionAbstract.js";
4
4
 
5
5
  /** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
6
6
 
7
- /** @typedef {Annnotations3} Annotations3InstanceType */
7
+ /** @typedef {Annotations3} Annotations3InstanceType */
8
8
 
9
9
  /**
10
10
  * @extends {CollectionAbstract}
11
11
  */
12
- class Annnotations3 extends CollectionAbstract {
12
+ class Annotations3 extends CollectionAbstract {
13
13
  /**
14
14
  * @param {FastifyInstanceType} fastify
15
15
  */
@@ -17,14 +17,10 @@ class Annnotations3 extends CollectionAbstract {
17
17
  super(fastify, "annotations3");
18
18
  }
19
19
 
20
- notImplementedError() {
21
- throw this.errorNoAction(`${this.constructor.name}: not implemented`);
22
- }
23
-
24
20
  }
25
21
 
26
22
  export default fastifyPlugin((fastify, options, done) => {
27
- fastify.decorate("annnotations3", new Annnotations3(fastify));
23
+ fastify.decorate("annotations3", new Annotations3(fastify));
28
24
  done();
29
25
  }, {
30
26
  name: "annotations3",
@@ -20,8 +20,8 @@ class CollectionAbstractError extends Error {
20
20
  */
21
21
  constructor(collectionName, operation, message, errInfo) {
22
22
  const
23
- collInfo = collectionName ? `on collection '${collectionName}',` : "",
24
- operationInfo = operation ? `when performing operation '${operation.toLocaleLowerCase()}'`: "";
23
+ collInfo = collectionName ? `on collection '${collectionName}'` : "",
24
+ operationInfo = operation ? `, when performing operation '${operation.toLocaleLowerCase()}'`: "";
25
25
  super(`CollectionAbstractError: ${collInfo} ${operationInfo}: ${message}`);
26
26
  this.info = errInfo;
27
27
  }
@@ -176,6 +176,10 @@ class CollectionAbstract {
176
176
  throw this.errorConstructor(operation)(err.message, err.errorResponse);
177
177
  }
178
178
 
179
+ notImplementedError() {
180
+ throw this.errorNoAction("not implemented");
181
+ }
182
+
179
183
  //////////////////////////////////////
180
184
  // INSERT/UPDATE
181
185
 
@@ -1,6 +1,6 @@
1
1
  import fastifyPlugin from "fastify-plugin"
2
2
 
3
- import { pathToUrl, ajvCompile, inspectObj, getFirstNonEmptyPair } from "#utils/utils.js";
3
+ import { pathToUrl, ajvCompile, inspectObj, getFirstNonEmptyPair, visibleLog } from "#utils/utils.js";
4
4
  import { returnError, makeResponsePostSchema } from "#utils/routeUtils.js";
5
5
 
6
6
  /** @typedef {import("#types").Manifests2InstanceType} Manifests2InstanceType */
@@ -53,22 +53,42 @@ function commonRoutes(fastify, options, done) {
53
53
  motivation: {
54
54
  type: "string",
55
55
  enum: ["painting", "non-painting", "commenting", "describing", "tagging", "linking"]
56
+ },
57
+ canvasMin: {
58
+ type: "integer",
59
+ minimum: 0
60
+ },
61
+ canvasMax: {
62
+ type: ["integer","null"],
63
+ minimum: 0
56
64
  }
57
65
  }
58
66
  },
59
67
  response: {
60
68
  200: iiifAnnotationListSchema
61
69
  }
70
+ },
71
+ preValidation: async (request, reply) => {
72
+ const
73
+ { canvasMin, canvasMax } = request.query,
74
+ error = new Error(`Error validating GET search-api route: 'canvasMin' must be smaller than 'canvasMax'. If 'canvasMax' is defined, 'canvasMin' must be defined as well. Got canvasMin=${canvasMin}, canvasMax=${canvasMax}`);
75
+ if (
76
+ (canvasMin == null && canvasMax != null)
77
+ || (canvasMin > canvasMax)
78
+ ) {
79
+ returnError(request, reply, error, {}, 400);
80
+ }
81
+ return;
62
82
  }
63
83
  },
64
84
  async (request, reply) => {
65
85
  const
66
86
  queryUrl = pathToUrl(request.url),
67
87
  { iiifSearchVersion, manifestShortId } = request.params,
68
- { q, motivation } = request.query;
88
+ { q, motivation, canvasMin, canvasMax } = request.query;
69
89
 
70
90
  if ( iiifSearchVersion===1 ) {
71
- return await annotations2.search(queryUrl, manifestShortId, q, motivation);
91
+ return await annotations2.search(queryUrl, manifestShortId, q, motivation, canvasMin, canvasMax);
72
92
  } else {
73
93
  annotations3.notImplementedError();
74
94
  }
@@ -32,6 +32,19 @@ test("test common routes", async (t) => {
32
32
  throw err;
33
33
  }
34
34
 
35
+ ////////////////////////////////////////////////
36
+ // GET routes
37
+
38
+ await t.test("test route /search-api/:iiifSearchVersion/manifests/:manifestShortId/search", async (t) => {
39
+
40
+ // TODO
41
+ // q
42
+ // motivation
43
+ // canvasMin
44
+ // canvasMax
45
+
46
+ })
47
+
35
48
  ////////////////////////////////////////////////
36
49
  // DELETE routes
37
50