@paulhectork/aiiinotate 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +206 -0
- package/cli/export.js +118 -0
- package/cli/import.js +111 -0
- package/cli/index.js +45 -0
- package/cli/migrate.js +131 -0
- package/cli/serve.js +29 -0
- package/cli/utils/fastifyClient.js +86 -0
- package/cli/utils/io.js +225 -0
- package/cli/utils/mongoClient.js +21 -0
- package/cli/utils/progressbar.js +86 -0
- package/cli/xywhToInt.js +99 -0
- package/config/.env.template +48 -0
- package/docker/Dockerfile +39 -0
- package/docker/README.md +5 -0
- package/docker/docker-compose.yaml +49 -0
- package/docker/docker.sh +43 -0
- package/docker/docker_aiiinotate_import.sh +62 -0
- package/docs/api.md +381 -0
- package/docs/cli.md +132 -0
- package/docs/dev_documentation/dev_architecture.md +88 -0
- package/docs/dev_documentation/dev_db.md +58 -0
- package/docs/dev_documentation/dev_iiif_compatibility.md +43 -0
- package/docs/dev_documentation/dev_notes_quirks_and_troubleshooting.md +143 -0
- package/docs/docker.md +98 -0
- package/docs/includes/report_benchmark_aiiinotate_2026-05-28-02:50:48_7steps.png +0 -0
- package/docs/scalability.md +34 -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 +428 -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 +30 -0
- package/migrations/baseConfig.js +57 -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 +48 -0
- package/migrations/migrationScripts/20250826194832-annotations2-schema.js +42 -0
- package/migrations/migrationScripts/20250904080710-annotations2-indexes.js +69 -0
- package/migrations/migrationScripts/20251002141951-manifests2-schema.js +43 -0
- package/migrations/migrationScripts/20251006212110-manifests2-indexes.js +35 -0
- package/migrations/migrationTemplate.js +25 -0
- package/package.json +82 -0
- package/scripts/get_version.py +12 -0
- package/scripts/run.sh +36 -0
- package/scripts/setup_mongodb.sh +70 -0
- package/scripts/setup_node.sh +15 -0
- package/scripts/update_version.py +30 -0
- package/scripts/utils.sh +65 -0
- package/src/app.js +116 -0
- package/src/constants.js +73 -0
- package/src/data/annotations/annotations2.js +681 -0
- package/src/data/annotations/annotations3.js +28 -0
- package/src/data/annotations/routes.js +335 -0
- package/src/data/annotations/routes.test.js +271 -0
- package/src/data/collectionAbstract.js +283 -0
- package/src/data/index.js +29 -0
- package/src/data/manifests/manifests2.js +378 -0
- package/src/data/manifests/manifests2.test.js +53 -0
- package/src/data/manifests/manifests3.js +23 -0
- package/src/data/manifests/routes.js +122 -0
- package/src/data/manifests/routes.test.js +70 -0
- package/src/data/routes.js +181 -0
- package/src/data/routes.test.js +166 -0
- package/src/db/index.js +50 -0
- package/src/fixtures/annotations.js +41 -0
- package/src/fixtures/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
- package/src/fixtures/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
- package/src/fixtures/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
- package/src/fixtures/data/annotations2Invalid.jsonld +39 -0
- package/src/fixtures/data/annotations2SvgValid.jsonld +81 -0
- package/src/fixtures/data/annotations2Valid.jsonld +39 -0
- package/src/fixtures/data/bnf_invalid_manifest.json +2806 -0
- package/src/fixtures/data/bnf_valid_manifest.json +2817 -0
- package/src/fixtures/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
- package/src/fixtures/generate.js +181 -0
- package/src/fixtures/index.js +69 -0
- package/src/fixtures/manifests.js +14 -0
- package/src/fixtures/utils.js +37 -0
- package/src/schemas/index.js +100 -0
- package/src/schemas/schemasBase.js +19 -0
- package/src/schemas/schemasPresentation2.js +410 -0
- package/src/schemas/schemasPresentation3.js +33 -0
- package/src/schemas/schemasResolver.js +71 -0
- package/src/schemas/schemasRoutes.js +318 -0
- package/src/server.js +25 -0
- package/src/types.js +97 -0
- package/src/utils/iiif2Utils.js +332 -0
- package/src/utils/iiif2Utils.test.js +146 -0
- package/src/utils/iiif3Utils.js +0 -0
- package/src/utils/iiifUtils.js +18 -0
- package/src/utils/logger.js +119 -0
- package/src/utils/routeUtils.js +137 -0
- package/src/utils/svg.js +417 -0
- package/src/utils/testUtils.js +289 -0
- package/src/utils/utils.js +403 -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,181 @@
|
|
|
1
|
+
import { v4 as uuid4 } from "uuid";
|
|
2
|
+
|
|
3
|
+
import { getRandomItem } from "#utils/utils.js";
|
|
4
|
+
import { PUBLIC_URL } from "#constants";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* generate an array of length `length` filled with values returned by `itemFunc`
|
|
8
|
+
* @param {number} length
|
|
9
|
+
* @param {Function} itemFunc
|
|
10
|
+
* @returns {Array}
|
|
11
|
+
*/
|
|
12
|
+
const fillArray = (length, itemFunc) =>
|
|
13
|
+
Array.from({ length: length }, itemFunc)
|
|
14
|
+
|
|
15
|
+
/** @returns {string} - matches "int,int,int.int" */
|
|
16
|
+
const makeXywh = () =>
|
|
17
|
+
fillArray(4, () => Math.floor(Math.random() * 1000))
|
|
18
|
+
.sort()
|
|
19
|
+
.join(",");
|
|
20
|
+
|
|
21
|
+
const makeRandomNumber = (maxNum=1000) => Math.floor(Math.random() * maxNum);
|
|
22
|
+
|
|
23
|
+
const makeManifestShortId = () =>
|
|
24
|
+
`wit${makeRandomNumber()}_pdf${makeRandomNumber()}_anno${makeRandomNumber()}`;
|
|
25
|
+
|
|
26
|
+
const makeBaseUrl = (manifestShortId) =>
|
|
27
|
+
`${PUBLIC_URL}/data/2/${manifestShortId}`;
|
|
28
|
+
|
|
29
|
+
const makeAnnotationId = (manifestShortId) =>
|
|
30
|
+
`${makeBaseUrl(manifestShortId)}/annotation/a_${uuid4()}`;
|
|
31
|
+
|
|
32
|
+
const makeManifestId = (manifestShortId) =>
|
|
33
|
+
`${makeBaseUrl(manifestShortId)}/manifest.json`;
|
|
34
|
+
|
|
35
|
+
const makeCanvasId = (manifestShortId) =>
|
|
36
|
+
`${makeBaseUrl(manifestShortId)}/canvas/c_${uuid4()}`;
|
|
37
|
+
|
|
38
|
+
const makeAnnotationListId = (manifestShortId) =>
|
|
39
|
+
`${makeBaseUrl(manifestShortId)}/list/l_${uuid4()}`;
|
|
40
|
+
|
|
41
|
+
const makeIiif2Manifest = (manifestShortId, canvasArray) => ({
|
|
42
|
+
"@context": "http://iiif.io/api/presentation/2/context.json",
|
|
43
|
+
"@id": makeManifestId(manifestShortId),
|
|
44
|
+
"label": "test aiiinotate manifest",
|
|
45
|
+
"attribution": "",
|
|
46
|
+
"seeAlso": [
|
|
47
|
+
"http://oai.bnf.fr/oai2/OAIHandler?verb=GetRecord&metadataPrefix=oai_dc&identifier=oai:bnf.fr:gallica/ark:/12148/btv1b8490076p"
|
|
48
|
+
],
|
|
49
|
+
"description": "this is a test IIIF manifest for aiiinotate testing purposes",
|
|
50
|
+
"metadata": [],
|
|
51
|
+
"sequences": [
|
|
52
|
+
{
|
|
53
|
+
"canvases": canvasArray,
|
|
54
|
+
"label": "Current Page Order",
|
|
55
|
+
"@type": "sc:Sequence",
|
|
56
|
+
"@id": `${makeManifestId(manifestShortId)}/sequence/default`
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"thumbnail": {
|
|
60
|
+
"@id": "https://gallica.bnf.fr/ark:/12148/btv1b8490076p.thumbnail"
|
|
61
|
+
},
|
|
62
|
+
"@type": "sc:Manifest",
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const makeIiif2Canvas = (canvasId) => ({
|
|
66
|
+
"@context": "http://iiif.io/api/presentation/2/context.json",
|
|
67
|
+
"@id": canvasId,
|
|
68
|
+
"label": "plat supérieur",
|
|
69
|
+
"height": 6044,
|
|
70
|
+
"width": 4768,
|
|
71
|
+
"images": [
|
|
72
|
+
{
|
|
73
|
+
"motivation": "sc:painting",
|
|
74
|
+
"on": canvasId,
|
|
75
|
+
"resource": {
|
|
76
|
+
"format": "image/jpeg",
|
|
77
|
+
"service": {
|
|
78
|
+
"profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level2",
|
|
79
|
+
"@context": "http://iiif.io/api/image/1/context.json",
|
|
80
|
+
"@id": `${canvasId}_img.png`
|
|
81
|
+
},
|
|
82
|
+
"height": 6044,
|
|
83
|
+
"width": 4768,
|
|
84
|
+
"@id": `${canvasId}_img.png/full/full/0/native.jpg`,
|
|
85
|
+
"@type": "dctypes:Image"
|
|
86
|
+
},
|
|
87
|
+
"@type": "oa:Annotation"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"@type": "sc:Canvas"
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const makeIiif2AnnotationList = (manifestShortId, annotationArray) => ({
|
|
94
|
+
"@context": "http://iiif.io/api/presentation/2/context.json",
|
|
95
|
+
"@type": "sc:AnnotationList",
|
|
96
|
+
"@id": makeAnnotationListId(manifestShortId),
|
|
97
|
+
"resources": annotationArray
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const makeIiif2Annotation = (annotationId, canvasId) => ({
|
|
101
|
+
"@context": "http://iiif.io/api/presentation/2/context.json",
|
|
102
|
+
"@id": annotationId,
|
|
103
|
+
"@type": "oa:Annotation",
|
|
104
|
+
"resource": {
|
|
105
|
+
"@type": "dctypes:Text",
|
|
106
|
+
"format": "text/html",
|
|
107
|
+
"chars": ""
|
|
108
|
+
},
|
|
109
|
+
"on": `${canvasId}#xywh=${makeXywh()}`,
|
|
110
|
+
"motivation": [
|
|
111
|
+
"oa:tagging",
|
|
112
|
+
"oa:commenting"
|
|
113
|
+
],
|
|
114
|
+
"label": ""
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {string} manifestShortId
|
|
119
|
+
* @param {string[]} canvasIdArray
|
|
120
|
+
* @returns {object}
|
|
121
|
+
*/
|
|
122
|
+
const generateIiif2AnnotationList = (manifestShortId, canvasIdArray) =>
|
|
123
|
+
makeIiif2AnnotationList(
|
|
124
|
+
canvasIdArray.map((canvasId) =>
|
|
125
|
+
makeIiif2Annotation(
|
|
126
|
+
makeAnnotationId(manifestShortId),
|
|
127
|
+
canvasId
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {number} nCanvas - integer, number of canvases in the manifest
|
|
135
|
+
* @returns {object}
|
|
136
|
+
*/
|
|
137
|
+
const generateIiif2Manifest = (nCanvas) => {
|
|
138
|
+
const manifestShortId = makeManifestShortId();
|
|
139
|
+
return makeIiif2Manifest(
|
|
140
|
+
manifestShortId,
|
|
141
|
+
fillArray(nCanvas, () => makeCanvasId(manifestShortId))
|
|
142
|
+
.map((canvasId) => makeIiif2Canvas(canvasId))
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* return an array of [manifest, annotationList].
|
|
148
|
+
* manifest has `nCanvas` canvases, `annotationList` has `nAnnotations` annotation on the canvases of the generated manifest.
|
|
149
|
+
* @param {number} nCanvas
|
|
150
|
+
* @param {number} nAnnotations
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
const generateIiif2ManifestAndAnnotationsList = (nCanvas, nAnnotations) => {
|
|
154
|
+
const
|
|
155
|
+
manifestShortId = makeManifestShortId(),
|
|
156
|
+
manifest = makeIiif2Manifest(
|
|
157
|
+
manifestShortId,
|
|
158
|
+
fillArray(nCanvas, () => makeCanvasId(manifestShortId))
|
|
159
|
+
.map((canvasId) => makeIiif2Canvas(canvasId))
|
|
160
|
+
),
|
|
161
|
+
canvasArray = fillArray(
|
|
162
|
+
nAnnotations,
|
|
163
|
+
() => getRandomItem(manifest.sequences[0].canvases)
|
|
164
|
+
).map((canvas) =>
|
|
165
|
+
makeIiif2Annotation(
|
|
166
|
+
makeAnnotationId(manifestShortId),
|
|
167
|
+
canvas["@id"]
|
|
168
|
+
)
|
|
169
|
+
),
|
|
170
|
+
annotationList = makeIiif2AnnotationList(
|
|
171
|
+
manifestShortId, canvasArray
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return [ manifest, annotationList ];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
generateIiif2Manifest,
|
|
179
|
+
generateIiif2AnnotationList,
|
|
180
|
+
generateIiif2ManifestAndAnnotationsList
|
|
181
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* the `fixtures` 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, annotations2SvgValid, annotationListUri, annotationListUriArray, annotationList, annotationListArray, annotationListUriInvalid, annotationListUriArrayInvalid } from "#src/fixtures/annotations.js";
|
|
7
|
+
import { manifest2Valid, manifest2ValidUri, manifest2Invalid, manifest2InvalidUri } from "#fixtures/manifests.js";
|
|
8
|
+
import { generateIiif2Manifest, generateIiif2AnnotationList, generateIiif2ManifestAndAnnotationsList } from "#fixtures/generate.js";
|
|
9
|
+
import { readFileToObject } from "#fixtures/utils.js";
|
|
10
|
+
|
|
11
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* NOTE: `done` musn't be used with async plugins. it raises an error `FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER`
|
|
16
|
+
* @param {FastifyInstanceType} fastify Encapsulated Fastify Instance
|
|
17
|
+
* @param {object} options
|
|
18
|
+
*/
|
|
19
|
+
async function fixtures(fastify, options) {
|
|
20
|
+
|
|
21
|
+
/** route to return a file in `dataDir` */
|
|
22
|
+
fastify.get(
|
|
23
|
+
"/fixtures/:fileName",
|
|
24
|
+
{
|
|
25
|
+
schema: {
|
|
26
|
+
params: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: { fileName: { type: "string" } }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
response: {
|
|
32
|
+
200: {
|
|
33
|
+
type: "string"
|
|
34
|
+
},
|
|
35
|
+
500: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
error: { type: "string" }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
async (request, reply) => {
|
|
44
|
+
const { fileName } = request.params;
|
|
45
|
+
return readFileToObject(fileName);
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
fastify.decorate("fixtures", {
|
|
50
|
+
annotations2SvgValid: annotations2SvgValid,
|
|
51
|
+
annotationListUri: annotationListUri,
|
|
52
|
+
annotationListUriArray: annotationListUriArray,
|
|
53
|
+
annotationList: annotationList,
|
|
54
|
+
annotationListArray: annotationListArray,
|
|
55
|
+
annotationListUriArrayInvalid: annotationListUriArrayInvalid,
|
|
56
|
+
annotationListUriInvalid: annotationListUriInvalid,
|
|
57
|
+
annotations2Invalid: annotations2Invalid,
|
|
58
|
+
annotations2Valid: annotations2Valid,
|
|
59
|
+
manifest2Valid: manifest2Valid,
|
|
60
|
+
manifest2ValidUri: manifest2ValidUri,
|
|
61
|
+
manifest2Invalid: manifest2Invalid,
|
|
62
|
+
manifest2InvalidUri: manifest2InvalidUri,
|
|
63
|
+
generateIiif2Manifest: generateIiif2Manifest,
|
|
64
|
+
generateIiif2AnnotationList: generateIiif2AnnotationList,
|
|
65
|
+
generateIiif2ManifestAndAnnotationsList: generateIiif2ManifestAndAnnotationsList
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default fastifyPlugin(fixtures);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileToObject, toUrl } from "#fixtures/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,37 @@
|
|
|
1
|
+
import url from "node:url";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
import { BASE_URL } from "#constants";
|
|
6
|
+
|
|
7
|
+
// path to dirctory of curent file
|
|
8
|
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
9
|
+
// path to fixtures/data
|
|
10
|
+
const dataDir = path.join(__dirname, "data");
|
|
11
|
+
|
|
12
|
+
const availableFiles = fs.readdirSync(dataDir);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* for simplicity, `readFileToObject` is synchronous. given that `fixtures` should only be used in tests, there is no performance downgrade for the prod server.
|
|
16
|
+
* @param {string} fn: the filename
|
|
17
|
+
* @returns {object}
|
|
18
|
+
*/
|
|
19
|
+
const readFileToObject = (fn) => {
|
|
20
|
+
if (!availableFiles.includes(fn)) {
|
|
21
|
+
throw new Error(`file not found: ${fn}`);
|
|
22
|
+
}
|
|
23
|
+
return JSON.parse(fs.readFileSync(path.join(dataDir, fn), { encoding: "utf8" }));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} fn
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
const toUrl = (fn) => `${BASE_URL}/fixtures/${fn}`;
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
dataDir,
|
|
35
|
+
readFileToObject,
|
|
36
|
+
toUrl
|
|
37
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fastifyPlugin from "fastify-plugin";
|
|
2
|
+
|
|
3
|
+
import addSchemasBase from "#schemas/schemasBase.js";
|
|
4
|
+
import addSchemasPresentation3 from "#schemas/schemasPresentation3.js";
|
|
5
|
+
import addSchemasPresentation2 from "#src/schemas/schemasPresentation2.js";
|
|
6
|
+
import addSchemasRoutes from "#schemas/schemasRoutes.js";
|
|
7
|
+
import schemasResolver from "#schemas/schemasResolver.js";
|
|
8
|
+
import { PUBLIC_URL } from "#constants";
|
|
9
|
+
|
|
10
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
11
|
+
/** @typedef {import("#types").FastifySchemaType} FastifySchemaType */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* NOTE : how are schemas managed in aiiinotate ?
|
|
15
|
+
* we implement "namespaced schemas" (aka, schemas grouped together in a single fastify decorator).
|
|
16
|
+
*
|
|
17
|
+
* TLDR: schemas are added to the global fastify instance, but their IDs are namespaced;
|
|
18
|
+
* schemas they are then accessed through decorators, each decorator is used to resolve annotations
|
|
19
|
+
* within a namespace.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* > fastify.schemasPresentation2.getSchema("annotations") =>
|
|
23
|
+
* > // resolves to
|
|
24
|
+
* > fastify.getSchema("$PUBLIC_URL/schemas/schemasPresentation2/annotation")
|
|
25
|
+
*
|
|
26
|
+
* IN MORE DETAIL:
|
|
27
|
+
* - namespacing is done through the IDs of the schemas, defined on `fastify.addSchema` and used
|
|
28
|
+
* to retrieve a schema with `fastify.getSchema()`
|
|
29
|
+
* - the global schema ID anatomy is defined here, in `_makeSchemaUri`: it's an URI that
|
|
30
|
+
* accepts a "namespace". each schema has a unique `slug` appended to the end of the URI.
|
|
31
|
+
* - each file in this module (appart from `schemasResolver` and this file) exports
|
|
32
|
+
* an `addSchemas` function that adds schemas to the global fastify instance with
|
|
33
|
+
* a specific namespace.
|
|
34
|
+
* - then, a decorator is added to the fastify instance for each namespace. it adds 2 functions
|
|
35
|
+
* `getSchema` and `makeSchemaUri` that are used to resolve schemas names and to access
|
|
36
|
+
* all schemas within a certain namespace.
|
|
37
|
+
* - thanks to that, schemas can be accessed by their unique slug with the pattern:
|
|
38
|
+
* `fastify.$schemaNamespace.getSchema($schemaSlug)`.
|
|
39
|
+
* - schemas defined here are accessible in the entire fastify app.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @type {(namespace: string) => (slug: string) => string}
|
|
44
|
+
*/
|
|
45
|
+
const _makeSchemaUri = (namespace) =>
|
|
46
|
+
(slug) => `${PUBLIC_URL}/schemas/${namespace}/${slug}`;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @type {(fastify: FastifyInstanceType, namespace: string) => (slug: string) => FastifySchemaType}
|
|
50
|
+
*/
|
|
51
|
+
const _getSchema = (fastify, namespace) =>
|
|
52
|
+
(slug) => fastify.getSchema(_makeSchemaUri(namespace)(slug));
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* decorate fastify with namespaced schemas
|
|
56
|
+
* @param {FastifyInstanceType} fastify
|
|
57
|
+
* @param {{ namespace: string, addSchemasFunc: Function }} options
|
|
58
|
+
*/
|
|
59
|
+
const addSchemasDecorator = (fastify, options) => {
|
|
60
|
+
if (!(options.namespace || "").length) {
|
|
61
|
+
throw new Error(`addSchemasDecorator requires options.namespace to be set ! Got '${options.namespace}'`);
|
|
62
|
+
}
|
|
63
|
+
if (!options.addSchemasFunc || typeof options.addSchemasFunc !== "function") {
|
|
64
|
+
throw new Error(`addSchemasDecorator requires options.addSchemasFunc to be a function ! Got '${options.addSchemasFunc}'`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const
|
|
68
|
+
{ namespace, addSchemasFunc } = options,
|
|
69
|
+
makeSchemaUri = _makeSchemaUri(namespace),
|
|
70
|
+
getSchema = _getSchema(fastify, namespace);
|
|
71
|
+
|
|
72
|
+
fastify = addSchemasFunc(fastify, makeSchemaUri);
|
|
73
|
+
fastify.decorate(namespace, {
|
|
74
|
+
makeSchemaUri: makeSchemaUri,
|
|
75
|
+
getSchema: getSchema
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function schemas(fastify, options, done) {
|
|
80
|
+
|
|
81
|
+
const schemasData = [
|
|
82
|
+
[ "schemasPresentation2", addSchemasPresentation2 ],
|
|
83
|
+
[ "schemasPresentation3", addSchemasPresentation3 ],
|
|
84
|
+
[ "schemasBase", addSchemasBase ],
|
|
85
|
+
[ "schemasRoutes", addSchemasRoutes ],
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
fastify.register(schemasResolver);
|
|
89
|
+
|
|
90
|
+
for (let [ schemasNamespace, addSchemasFunc ] of schemasData) {
|
|
91
|
+
addSchemasDecorator(fastify, {
|
|
92
|
+
namespace: schemasNamespace,
|
|
93
|
+
addSchemasFunc: addSchemasFunc,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
done()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default fastifyPlugin(schemas);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
2
|
+
|
|
3
|
+
function addSchemas(fastify, makeSchemaUri) {
|
|
4
|
+
fastify.addSchema({
|
|
5
|
+
$id: makeSchemaUri("presentation"),
|
|
6
|
+
type: "integer",
|
|
7
|
+
enum: [ 2, 3 ],
|
|
8
|
+
description: "IIIF presentation API versions"
|
|
9
|
+
});
|
|
10
|
+
fastify.addSchema({
|
|
11
|
+
$id: makeSchemaUri("search"),
|
|
12
|
+
type: "integer",
|
|
13
|
+
enum: [ 1, 2 ],
|
|
14
|
+
description: "IIIF search API versions"
|
|
15
|
+
});
|
|
16
|
+
return fastify
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default addSchemas;
|