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 @@
|
|
|
1
|
+
{"@type": "sc:AnnotationList", "resources": [{"@id": "http://iscd.huma-num.fr/sas/annotation/wit253_man253_anno253_c24_b2204564e1d74023bc38cbd2906e137b", "@type": "oa:Annotation", "dcterms:created": "2025-09-09T11:42:43", "dcterms:modified": "2025-09-09T11:42:43", "resource": [{"@type": "dctypes:Text", "https://iscd.huma-num.fr/sas/full_text": "", "format": "text/html", "chars": "<p></p>"}], "on": [{"@type": "oa:SpecificResource", "within": {"@id": "https://iscd.huma-num.fr/vhs/iiif/v2/wit253_man253_anno253/manifest.json", "@type": "sc:Manifest"}, "selector": {"@type": "oa:Choice", "default": {"@type": "oa:FragmentSelector", "value": "xywh=212,381,1021,1610"}, "item": {"@type": "oa:SvgSelector", "value": "<svg xmlns=\"http://www.w3.org/2000/svg\"><path xmlns='http://www.w3.org/2000/svg' d='M212 381 h 510 v 0 h 510 v 805 v 805 h -510 h -510 v -805Z' id='rectangle_wit253_man253_anno253_c24_b2204564e1d74023bc38cbd2906e137b' data-paper-data='{"strokeWidth":1,"rotation":0,"annotation":null,"nonHoverStrokeColor":["Color",0,1,0],"editable":true,"deleteIcon":null,"rotationIcon":null,"group":null}' fill-opacity='0' fill='#00ff00' fill-rule='nonzero' stroke='#00ff00' stroke-width='1' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='10' stroke-dashoffset='0' style='mix-blend-mode: normal'/></svg>"}}, "full": "https://iscd.huma-num.fr/vhs/iiif/v2/wit253_man253_anno253/canvas/c24.json"}], "motivation": ["oa:commenting", "oa:tagging"], "@context": "http://iiif.io/api/presentation/2/context.json"}]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* the `fileServer` plugin makes test files available to the entire fastify app, mostly for testing purposes.
|
|
3
|
+
*/
|
|
4
|
+
import fastifyPlugin from "fastify-plugin";
|
|
5
|
+
|
|
6
|
+
import { annotations2Invalid, annotations2Valid, annotationListUri, annotationListUriArray, annotationList, annotationListArray, annotationListUriInvalid, annotationListUriArrayInvalid } from "#src/fileServer/annotations.js";
|
|
7
|
+
import { manifest2Valid, manifest2ValidUri, manifest2Invalid, manifest2InvalidUri } from "#fileServer/manifests.js";
|
|
8
|
+
import { readFileToObject } from "#fileServer/utils.js";
|
|
9
|
+
|
|
10
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* NOTE: `done` musn't be used with async plugins. it raises an error `FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER`
|
|
15
|
+
* @param {FastifyInstanceType} fastify Encapsulated Fastify Instance
|
|
16
|
+
* @param {object} options
|
|
17
|
+
*/
|
|
18
|
+
async function fileServer(fastify, options) {
|
|
19
|
+
|
|
20
|
+
/** route to return a file in `dataDir` */
|
|
21
|
+
fastify.get(
|
|
22
|
+
"/fileServer/:fileName",
|
|
23
|
+
{
|
|
24
|
+
schema: {
|
|
25
|
+
params: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: { fileName: { type: "string" } }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
response: {
|
|
31
|
+
200: {
|
|
32
|
+
type: "string"
|
|
33
|
+
},
|
|
34
|
+
500: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
error: { type: "string" }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
(request, reply) => {
|
|
43
|
+
const { fileName } = request.params;
|
|
44
|
+
return readFileToObject(fileName);
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
fastify.decorate("fileServer", {
|
|
49
|
+
annotationListUri: annotationListUri,
|
|
50
|
+
annotationListUriArray: annotationListUriArray,
|
|
51
|
+
annotationList: annotationList,
|
|
52
|
+
annotationListArray: annotationListArray,
|
|
53
|
+
annotationListUriArrayInvalid: annotationListUriArrayInvalid,
|
|
54
|
+
annotationListUriInvalid: annotationListUriInvalid,
|
|
55
|
+
annotations2Invalid: annotations2Invalid,
|
|
56
|
+
annotations2Valid: annotations2Valid,
|
|
57
|
+
manifest2Valid: manifest2Valid,
|
|
58
|
+
manifest2ValidUri: manifest2ValidUri,
|
|
59
|
+
manifest2Invalid: manifest2Invalid,
|
|
60
|
+
manifest2InvalidUri: manifest2InvalidUri
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default fastifyPlugin(fileServer);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileToObject, toUrl } from "#fileServer/utils.js";
|
|
2
|
+
|
|
3
|
+
const manifest2ValidUri = { uri: toUrl("bnf_valid_manifest.json") };
|
|
4
|
+
const manifest2Valid = readFileToObject("bnf_valid_manifest.json");
|
|
5
|
+
const manifest2InvalidUri = { uri: toUrl("bnf_invalid_manifest.json") };
|
|
6
|
+
const manifest2Invalid = readFileToObject("bnf_invalid_manifest.json");
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
manifest2ValidUri,
|
|
11
|
+
manifest2Valid,
|
|
12
|
+
manifest2InvalidUri,
|
|
13
|
+
manifest2Invalid
|
|
14
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import url from "node:url";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
// path to dirctory of curent file
|
|
6
|
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
7
|
+
// path to fileServer/data
|
|
8
|
+
const dataDir = path.join(__dirname, "data");
|
|
9
|
+
|
|
10
|
+
const availableFiles = fs.readdirSync(dataDir);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* for simplicity, `readFileToObject` is synchronous. given that `fileServer` should only be used in tests, there is no performance downgrade for the prod server.
|
|
14
|
+
* @param {string} fn: the filename
|
|
15
|
+
* @returns {object}
|
|
16
|
+
*/
|
|
17
|
+
const readFileToObject = (fn) => {
|
|
18
|
+
if ( !availableFiles.includes(fn) ) {
|
|
19
|
+
throw new Error(`file not found: ${fn}`);
|
|
20
|
+
}
|
|
21
|
+
return JSON.parse(fs.readFileSync(path.join(dataDir, fn), { encoding: "utf8" }));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} fn
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
const toUrl = (fn) => `${process.env.APP_BASE_URL}/fileServer/${fn}`;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
dataDir,
|
|
33
|
+
readFileToObject,
|
|
34
|
+
toUrl
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fastifyPlugin from "fastify-plugin";
|
|
2
|
+
|
|
3
|
+
import schemasBase from "#src/schemas/schemasBase.js";
|
|
4
|
+
import schemasPresentation2 from "#src/schemas/schemasPresentation2.js";
|
|
5
|
+
import schemasPresentation3 from "#src/schemas/schemasPresentation3.js";
|
|
6
|
+
import schemasResolver from "#schemas/schemasResolver.js";
|
|
7
|
+
import schemasRoutes from "#src/schemas/schemasRoutes.js";
|
|
8
|
+
|
|
9
|
+
function schemas(fastify, options, done) {
|
|
10
|
+
|
|
11
|
+
fastify.register(schemasResolver);
|
|
12
|
+
fastify.register(schemasBase);
|
|
13
|
+
fastify.register(schemasPresentation2);
|
|
14
|
+
fastify.register(schemasPresentation3);
|
|
15
|
+
fastify.register(schemasRoutes);
|
|
16
|
+
|
|
17
|
+
done()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default fastifyPlugin(schemas);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fastifyPlugin from "fastify-plugin"
|
|
2
|
+
|
|
3
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
4
|
+
|
|
5
|
+
/** @param {"search"|"presentation"} slug */
|
|
6
|
+
const makeSchemaUri = (slug) =>
|
|
7
|
+
`${process.env.APP_BASE_URL}/schemas/${slug}/version`;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {FastifyInstanceType} fastify
|
|
11
|
+
* @param {"search"|"presentation"} slug
|
|
12
|
+
*/
|
|
13
|
+
const getSchema = (fastify, slug) =>
|
|
14
|
+
fastify.getSchema(makeSchemaUri(slug));
|
|
15
|
+
|
|
16
|
+
function addSchemas(fastify, options, done) {
|
|
17
|
+
|
|
18
|
+
// fastify.decorate("makeSchemaUri", makeSchemaUri);
|
|
19
|
+
// fastify.decorate("getSchema", (slug) => getSchema(fastify, slug));
|
|
20
|
+
|
|
21
|
+
// schemas are defined on the global `fastify` instance
|
|
22
|
+
fastify.addSchema({
|
|
23
|
+
$id: makeSchemaUri("presentation"),
|
|
24
|
+
type: "integer",
|
|
25
|
+
enum: [2, 3],
|
|
26
|
+
description: "IIIF presentation API versions"
|
|
27
|
+
});
|
|
28
|
+
fastify.addSchema({
|
|
29
|
+
$id: makeSchemaUri("search"),
|
|
30
|
+
type: "integer",
|
|
31
|
+
enum: [1, 2],
|
|
32
|
+
description: "IIIF search API versions"
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// functions `makeSchemaUri` and `getSchema`
|
|
36
|
+
// are defined in an object that is used to decorate the global `fastify` instance,
|
|
37
|
+
// this namespacing the functions and allowing each plugin
|
|
38
|
+
// in this module to have functions with the same name.
|
|
39
|
+
fastify.decorate("schemasBase", {
|
|
40
|
+
makeSchemaUri: makeSchemaUri,
|
|
41
|
+
getSchema: (slug) => getSchema(fastify, slug)
|
|
42
|
+
}) ;
|
|
43
|
+
|
|
44
|
+
done()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default fastifyPlugin(addSchemas);
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import fastifyPlugin from "fastify-plugin";
|
|
2
|
+
|
|
3
|
+
import { IIIF_PRESENTATION_2, IIIF_PRESENTATION_2_CONTEXT } from "#utils/iiifUtils.js";
|
|
4
|
+
|
|
5
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
6
|
+
|
|
7
|
+
// TODO: schemas are maybe wayyy too strict.
|
|
8
|
+
// convert all enums (the arrays below) to { type: string } ?
|
|
9
|
+
|
|
10
|
+
const oaSelectorTypes = [
|
|
11
|
+
"oa:FragmentSelector",
|
|
12
|
+
"oa:CssSelector",
|
|
13
|
+
"oa:XPathSelector",
|
|
14
|
+
"oa:TextQuoteSelector",
|
|
15
|
+
"oa:TextPositionSelector",
|
|
16
|
+
"oa:DataPositionSelector",
|
|
17
|
+
"oa:SvgSelector",
|
|
18
|
+
"oa:RangeSelector",
|
|
19
|
+
"cnt:ContentAsText", // this one is only described in IIIF Presentation API.
|
|
20
|
+
// also allow values without extension
|
|
21
|
+
"FragmentSelector",
|
|
22
|
+
"CssSelector",
|
|
23
|
+
"XPathSelector",
|
|
24
|
+
"TextQuoteSelector",
|
|
25
|
+
"TextPositionSelector",
|
|
26
|
+
"DataPositionSelector",
|
|
27
|
+
"SvgSelector",
|
|
28
|
+
"RangeSelector",
|
|
29
|
+
"ContentAsText"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const motivationValues = [
|
|
33
|
+
"sc:painting",
|
|
34
|
+
"oa:commenting",
|
|
35
|
+
"oa:describing",
|
|
36
|
+
"oa:tagging",
|
|
37
|
+
"oa:linking",
|
|
38
|
+
"painting",
|
|
39
|
+
"commenting",
|
|
40
|
+
"describing",
|
|
41
|
+
"tagging",
|
|
42
|
+
"linking",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
const embeddedBodyTypeValues = [
|
|
46
|
+
"oa:TextualBody",
|
|
47
|
+
"cnt:ContentAsText",
|
|
48
|
+
"dctypes:Text",
|
|
49
|
+
"oa:Tag",
|
|
50
|
+
"TextualBody",
|
|
51
|
+
"ContentAsText",
|
|
52
|
+
"Text",
|
|
53
|
+
"Tag",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
/** @param {string} slug */
|
|
57
|
+
const makeSchemaUri = (slug) =>
|
|
58
|
+
`${process.env.APP_BASE_URL}/schemas/presentation/${IIIF_PRESENTATION_2}/${slug}`
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {FastifyInstanceType} fastify
|
|
62
|
+
* @param {"search"|"presentation"} slug
|
|
63
|
+
*/
|
|
64
|
+
const getSchema = (fastify, slug) =>
|
|
65
|
+
fastify.getSchema(makeSchemaUri(slug))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
function addSchemas(fastify, options, done) {
|
|
69
|
+
|
|
70
|
+
/////////////////////////////////////////////
|
|
71
|
+
// GENERIC STUFF
|
|
72
|
+
|
|
73
|
+
fastify.addSchema({
|
|
74
|
+
$id: makeSchemaUri("context"),
|
|
75
|
+
type: "string",
|
|
76
|
+
enum: [ IIIF_PRESENTATION_2_CONTEXT["@context"] ]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/////////////////////////////////////////////
|
|
80
|
+
// SPECIFIC RESOURCES
|
|
81
|
+
|
|
82
|
+
// derived from: https://iiif.io/api/annex/openannotation/context.json
|
|
83
|
+
fastify.addSchema({
|
|
84
|
+
$id: makeSchemaUri("iiifImageApiSelector"),
|
|
85
|
+
type: "object",
|
|
86
|
+
required: [ "@id", "@type", "@context" ],
|
|
87
|
+
properties: {
|
|
88
|
+
"@id": { type: "string" },
|
|
89
|
+
"@type": {
|
|
90
|
+
type: "string",
|
|
91
|
+
enum: [ "iiif:ImageApiSelector" ]
|
|
92
|
+
},
|
|
93
|
+
"@context": { $ref: makeSchemaUri("context") },
|
|
94
|
+
region: { type: "string" },
|
|
95
|
+
size: { type: "string" },
|
|
96
|
+
rotation: { type: "string" },
|
|
97
|
+
format: { type: "string" },
|
|
98
|
+
quality: { type: "string" },
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// NOTE: we don't support refinedBy and any other recursive selectors.
|
|
103
|
+
// https://github.com/Aikon-platform/aiiinotate/blob/main/docs/specifications/0_w3c_web_annotations.md#selectors-data-model
|
|
104
|
+
fastify.addSchema({
|
|
105
|
+
$id: makeSchemaUri("oaSelector"),
|
|
106
|
+
type: "object",
|
|
107
|
+
required: [ "@type" ], // could also add `value` or `chars` but one or the other may be used, not both.
|
|
108
|
+
properties: {
|
|
109
|
+
"@id": { type: "string" },
|
|
110
|
+
"@type": {
|
|
111
|
+
anyOf: [
|
|
112
|
+
{
|
|
113
|
+
type: "string",
|
|
114
|
+
enum: oaSelectorTypes
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
// IIIF 2.1 has examples with multiple `@types`: // `chars` is used by SvgSelector in: https://iiif.io/api/presentation/2.1/#non-rectangular-segments
|
|
118
|
+
type: "array",
|
|
119
|
+
items: {
|
|
120
|
+
type: "string",
|
|
121
|
+
enum: oaSelectorTypes
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
value: { type: "string" },
|
|
127
|
+
chars: { type: "string" } // `chars` is used by SvgSelector in: https://iiif.io/api/presentation/2.1/#non-rectangular-segments
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
fastify.addSchema({
|
|
132
|
+
$id: makeSchemaUri("oaOrIiifSelector"),
|
|
133
|
+
type: "object",
|
|
134
|
+
oneOf: [
|
|
135
|
+
{ $ref: makeSchemaUri("oaSelector") },
|
|
136
|
+
{ $ref: makeSchemaUri("iiifImageApiSelector") },
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
fastify.addSchema({
|
|
141
|
+
$id: makeSchemaUri("oaChoiceSelector"),
|
|
142
|
+
type: "object",
|
|
143
|
+
required: [ "@type", "default" ],
|
|
144
|
+
properties: {
|
|
145
|
+
"@type": { type: "string", enum: ["oa:Choice"] },
|
|
146
|
+
default: { $ref: makeSchemaUri("oaOrIiifSelector") },
|
|
147
|
+
item: { $ref: makeSchemaUri("oaOrIiifSelector") }
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// selector is either a string, string[], iiifImageApiSelector, iiifImageApiSelector[]. oaSelector, oaSelector[]
|
|
152
|
+
fastify.addSchema({
|
|
153
|
+
$id: makeSchemaUri("selector"),
|
|
154
|
+
anyOf: [
|
|
155
|
+
{ type: "string" },
|
|
156
|
+
{ type: "array", items: { type: "string" } },
|
|
157
|
+
{ $ref: makeSchemaUri("oaOrIiifSelector") },
|
|
158
|
+
{ $ref: makeSchemaUri("oaChoiceSelector") },
|
|
159
|
+
{
|
|
160
|
+
type: "array",
|
|
161
|
+
items: { $ref: makeSchemaUri("oaOrIiifSelector") }
|
|
162
|
+
},
|
|
163
|
+
]
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
fastify.addSchema({
|
|
167
|
+
$id: makeSchemaUri("specificResource"),
|
|
168
|
+
type: "object",
|
|
169
|
+
required: [ "@type", "full", "selector" ],
|
|
170
|
+
properties: {
|
|
171
|
+
"@id": { type: "string" },
|
|
172
|
+
"@type": {
|
|
173
|
+
type: "string",
|
|
174
|
+
enum: [ "oa:SpecificResource" ]
|
|
175
|
+
},
|
|
176
|
+
// NOTE: OA defines a `source` field for SpecificResources, but in IIIF `full` seems to have the same role
|
|
177
|
+
// https://github.com/Aikon-platform/aiiinotate/blob/main/docs/specifications/0_w3c_web_annotations.md#specific-resources-data-model
|
|
178
|
+
full: {
|
|
179
|
+
anyOf: [
|
|
180
|
+
// URI
|
|
181
|
+
{ type: "string" },
|
|
182
|
+
// object describing an image
|
|
183
|
+
{
|
|
184
|
+
type: "object",
|
|
185
|
+
required: [ "@id", "@type" ],
|
|
186
|
+
properties: {
|
|
187
|
+
"@id": { type: "string" },
|
|
188
|
+
"@type": { type: "string" }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
selector: { $ref: makeSchemaUri("selector") },
|
|
194
|
+
purpose: { type: "string" }
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
/////////////////////////////////////////////
|
|
199
|
+
// ANNOTATIONS
|
|
200
|
+
// NOTE : annotations can define both painting and non-painting annotations.
|
|
201
|
+
|
|
202
|
+
fastify.addSchema({
|
|
203
|
+
$id: makeSchemaUri("annotationTarget"),
|
|
204
|
+
anyOf: [
|
|
205
|
+
// URI
|
|
206
|
+
{ type: "string" },
|
|
207
|
+
// SpecificResource
|
|
208
|
+
{ $ref: makeSchemaUri("specificResource") }
|
|
209
|
+
]
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
fastify.addSchema({
|
|
213
|
+
$id: makeSchemaUri("motivation"),
|
|
214
|
+
anyOf: [
|
|
215
|
+
{ type: "string", enum: motivationValues },
|
|
216
|
+
{
|
|
217
|
+
type: "array",
|
|
218
|
+
items : { type: "string", enum: motivationValues }
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
fastify.addSchema({
|
|
224
|
+
$id: makeSchemaUri("referencedBody"),
|
|
225
|
+
type: "object",
|
|
226
|
+
required: [ "@id", "@type" ],
|
|
227
|
+
properties: {
|
|
228
|
+
"@id": { type: "string" },
|
|
229
|
+
"@type": { type: "string" }, // should match `dctypes:[a-zA-Z]+`. regex is disabled for performance reasons.
|
|
230
|
+
"format": { type: "string" } // should be a MimeType.
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// embedded textual body
|
|
235
|
+
fastify.addSchema({
|
|
236
|
+
$id: makeSchemaUri("embeddedBody"),
|
|
237
|
+
type: "object",
|
|
238
|
+
required: [ "@type", "chars" ],
|
|
239
|
+
properties: {
|
|
240
|
+
"@type": {
|
|
241
|
+
anyOf: [
|
|
242
|
+
{
|
|
243
|
+
type: "string",
|
|
244
|
+
enum: embeddedBodyTypeValues
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: "array",
|
|
248
|
+
items: {
|
|
249
|
+
type: "string",
|
|
250
|
+
enum: embeddedBodyTypeValues
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
chars: { type: "string" },
|
|
256
|
+
motivation: { type: "string" },
|
|
257
|
+
format: { type: "string" }, // should be a MimeType
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
fastify.addSchema({
|
|
262
|
+
$id: makeSchemaUri("body"),
|
|
263
|
+
oneOf: [
|
|
264
|
+
{ $ref: makeSchemaUri("embeddedBody") },
|
|
265
|
+
{ $ref: makeSchemaUri("referencedBody") },
|
|
266
|
+
{
|
|
267
|
+
type: "array",
|
|
268
|
+
items: {
|
|
269
|
+
type: "object",
|
|
270
|
+
anyOf: [
|
|
271
|
+
{ $ref: makeSchemaUri("embeddedBody") },
|
|
272
|
+
{ $ref: makeSchemaUri("referencedBody") },
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
// NOTE: maeData is used by `mirador-annotations-editor` (MAE) to store data specific to that mirador plugin. since MAE is external to the Aikon ecosystem, we don't define it further to avoid errors if/when its data format changes.
|
|
280
|
+
// we set additionalProperties: true, because otherwise, writing annotations to database will be fine, but sending responses will strip out the contents of maeData.
|
|
281
|
+
fastify.addSchema({
|
|
282
|
+
$id: makeSchemaUri("maeData"),
|
|
283
|
+
additionalProperties: true,
|
|
284
|
+
type: "object"
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
fastify.addSchema({
|
|
288
|
+
$id: makeSchemaUri("annotation"),
|
|
289
|
+
type: "object",
|
|
290
|
+
required: [ "@id", "@context", "@type", "motivation", "on" ],
|
|
291
|
+
properties: {
|
|
292
|
+
"@id": { type: "string" },
|
|
293
|
+
"@context": { $ref: makeSchemaUri("context") },
|
|
294
|
+
"@type": { type: "string", enum: [ "oa:Annotation" ] },
|
|
295
|
+
motivation: { $ref: makeSchemaUri("motivation") },
|
|
296
|
+
on: { $ref: makeSchemaUri("annotationTarget") },
|
|
297
|
+
// in OA, one OR the other should be use, but `oneOf` can't be used in `properties`.
|
|
298
|
+
resource: { $ref: makeSchemaUri("body") },
|
|
299
|
+
bodyValue: { type: "string" },
|
|
300
|
+
maeData: { $ref: makeSchemaUri("maeData") }
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
fastify.addSchema({
|
|
305
|
+
$id: makeSchemaUri("annotationList"),
|
|
306
|
+
type: "object",
|
|
307
|
+
required: ["@id", "@type", "@context", "resources"],
|
|
308
|
+
properties: {
|
|
309
|
+
"@id": { type: "string" },
|
|
310
|
+
"@context": { $ref: makeSchemaUri("context") },
|
|
311
|
+
"@type": {
|
|
312
|
+
type: "string",
|
|
313
|
+
enum: [ "sc:AnnotationList" ]
|
|
314
|
+
},
|
|
315
|
+
"resources": {
|
|
316
|
+
type: "array",
|
|
317
|
+
items: { $ref: makeSchemaUri("annotation") }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// NOTE : not sure it's needed.
|
|
323
|
+
fastify.addSchema({
|
|
324
|
+
$id: makeSchemaUri("annotationArray"),
|
|
325
|
+
type: "array",
|
|
326
|
+
items: { $ref: makeSchemaUri("annotation") }
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
/////////////////////////////////////////////
|
|
330
|
+
// MANIFESTS
|
|
331
|
+
|
|
332
|
+
// internal data model for IIIF manifests, containing just what we need.
|
|
333
|
+
// manifests are just stored as an @id, a short ID, an array of canvas Ids. we don't need more info.
|
|
334
|
+
fastify.addSchema({
|
|
335
|
+
$id: makeSchemaUri("manifestMongo"),
|
|
336
|
+
type: "object",
|
|
337
|
+
required: ["@id", "@type", "manifestShortId", "canvasIds"],
|
|
338
|
+
properties: {
|
|
339
|
+
"@id": { type: "string" },
|
|
340
|
+
"@type": { type: "string", enum: ["sc:Manifest"] },
|
|
341
|
+
manifestShortId: { type: "string" },
|
|
342
|
+
canvasIds: { type: "array", items: { type: "string" }}
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
// minimal structure we need to work with a IIIF 2.x manifest.
|
|
347
|
+
fastify.addSchema({
|
|
348
|
+
$id: makeSchemaUri("manifestPublic"),
|
|
349
|
+
type: "object",
|
|
350
|
+
required: ["@id", "sequences"],
|
|
351
|
+
properties: {
|
|
352
|
+
"@id": { type: "string" },
|
|
353
|
+
sequences: {
|
|
354
|
+
type: "array",
|
|
355
|
+
items: {
|
|
356
|
+
type: "object",
|
|
357
|
+
required: [ "@id", "canvases" ],
|
|
358
|
+
properties: {
|
|
359
|
+
"@id": { type: "string" },
|
|
360
|
+
canvases: {
|
|
361
|
+
type: "array",
|
|
362
|
+
items: {
|
|
363
|
+
type: "object",
|
|
364
|
+
required: [ "@id" ],
|
|
365
|
+
properties: {
|
|
366
|
+
"@id": { type: "string" }
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
/////////////////////////////////////////////
|
|
377
|
+
// COLLETION
|
|
378
|
+
|
|
379
|
+
fastify.addSchema({
|
|
380
|
+
$id: makeSchemaUri("collection"),
|
|
381
|
+
type: "object",
|
|
382
|
+
required: [ "@id", "@type", "@context", "members" ],
|
|
383
|
+
properties: {
|
|
384
|
+
"@context": { $ref: makeSchemaUri("context") },
|
|
385
|
+
"@type": { type: "string", enum: [ "sc:Collection" ] },
|
|
386
|
+
"@id": { type: "string" },
|
|
387
|
+
label: { type: "string" },
|
|
388
|
+
members: {
|
|
389
|
+
type: "array",
|
|
390
|
+
items: {
|
|
391
|
+
type: "object",
|
|
392
|
+
required: ["@id"],
|
|
393
|
+
properties: {
|
|
394
|
+
"@id": { type: "string" },
|
|
395
|
+
"@type": { type: "string", enum: [ "sc:Manifest" ] },
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
/////////////////////////////////////////////
|
|
403
|
+
// DONE
|
|
404
|
+
|
|
405
|
+
fastify.decorate("schemasPresentation2", {
|
|
406
|
+
makeSchemaUri: makeSchemaUri,
|
|
407
|
+
getSchema: (slug) => getSchema(fastify, slug)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
done();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
export default fastifyPlugin(addSchemas)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fastifyPlugin from "fastify-plugin";
|
|
2
|
+
|
|
3
|
+
import { IIIF_PRESENTATION_3, IIIF_PRESENTATION_3_CONTEXT } from "#utils/iiifUtils.js";
|
|
4
|
+
|
|
5
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
6
|
+
|
|
7
|
+
/** @param {string} slug */
|
|
8
|
+
const makeSchemaUri = (slug) =>
|
|
9
|
+
`${process.env.APP_BASE_URL}/schemas/presentation/${IIIF_PRESENTATION_3}/${slug}`
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {FastifyInstanceType} fastify
|
|
13
|
+
* @param {"search"|"presentation"} slug
|
|
14
|
+
*/
|
|
15
|
+
const getSchema = (fastify, slug) =>
|
|
16
|
+
fastify.getSchema(makeSchemaUri(slug))
|
|
17
|
+
|
|
18
|
+
function addSchemas(fastify, options, done) {
|
|
19
|
+
|
|
20
|
+
fastify.addSchema({
|
|
21
|
+
$id: makeSchemaUri("context"),
|
|
22
|
+
type: "string",
|
|
23
|
+
enum: [ IIIF_PRESENTATION_3_CONTEXT["@context"] ]
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// minimal schema for IIIF manifests3, containing just what we need to process a manifest
|
|
27
|
+
fastify.addSchema({
|
|
28
|
+
$id: makeSchemaUri("manifestPublic"),
|
|
29
|
+
type: "object",
|
|
30
|
+
required: [ "@context", "id", "items" ],
|
|
31
|
+
properties: {
|
|
32
|
+
"@context": { $ref: makeSchemaUri("context") },
|
|
33
|
+
id: { type: "string" },
|
|
34
|
+
items: {
|
|
35
|
+
type: "array",
|
|
36
|
+
items: {
|
|
37
|
+
type: "object",
|
|
38
|
+
required: [ "id" ],
|
|
39
|
+
properties: {
|
|
40
|
+
id: { type: "string" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
fastify.decorate("schemasPresentation3", {
|
|
48
|
+
makeSchemaUri: makeSchemaUri,
|
|
49
|
+
getSchema: (slug) => getSchema(fastify, slug)
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
done()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default fastifyPlugin(addSchemas);
|
|
56
|
+
|
|
57
|
+
|