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,119 @@
|
|
|
1
|
+
# SIMPLE ANNOTATION SERVER
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Sources
|
|
6
|
+
|
|
7
|
+
- [official docs](https://github.com/glenrobson/SimpleAnnotationServer/tree/master/doc)
|
|
8
|
+
- [endpoints](https://github.com/glenrobson/SimpleAnnotationServer/blob/master/doc/Endpoints.md)
|
|
9
|
+
- [IIIF annotations search](https://github.com/glenrobson/SimpleAnnotationServer/blob/master/doc/IIIFSearch.md)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What does SAS do ?
|
|
14
|
+
|
|
15
|
+
From what I gather, SAS stores 2 types of data:
|
|
16
|
+
- IIIF annotations
|
|
17
|
+
- IIIF manifests
|
|
18
|
+
- annotations and manifests seem to follow the IIIF 2.x presentation API (instead of the most recent 3.0).
|
|
19
|
+
|
|
20
|
+
SAS has 2 main functionnalities:
|
|
21
|
+
- "backend": store and serve annotations in JSON format
|
|
22
|
+
- "frontend": provide a GUI to manage IIIF collections and, most importantly, collections of IIIF annotations.
|
|
23
|
+
- SAS uses Mirador as its IIIF graphical interface.
|
|
24
|
+
- SAS does have a Mirador plugin but a custom version of Mirador.
|
|
25
|
+
|
|
26
|
+
In turn, annotations are the primary ressources stored by SAS. SAS also needs to store manifests, so that it can enrich the manifests with the annitations, in order to send a complete manifest (a manifest in which annotations are either referenced or embdded).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Endpoints
|
|
31
|
+
|
|
32
|
+
### Fetch data
|
|
33
|
+
|
|
34
|
+
#### IIIF search API
|
|
35
|
+
```
|
|
36
|
+
GET /search-api/$manifestShortId/search
|
|
37
|
+
```
|
|
38
|
+
- **returns**: `AnnotationList` with an empty `@id`
|
|
39
|
+
- `$manifestShortId` is the manifest's ID
|
|
40
|
+
- search parameters are [as per the IIIF docs](https://iiif.io/api/search/2.0/#request-1):
|
|
41
|
+
- `q`: query string, searched in the annotation's textual body or its URI
|
|
42
|
+
- `motivation`: search through each annotation's `motivation` key
|
|
43
|
+
- `date`: date ranges
|
|
44
|
+
- `users`: the usrs who edited the annotation
|
|
45
|
+
- if no parameters are supplied, all annotations are returned
|
|
46
|
+
- *example: https://aikon.enpc.fr/sas/search-api/wit9_man11_anno165/search?q=*
|
|
47
|
+
|
|
48
|
+
#### Show all annotations for a canvas
|
|
49
|
+
```
|
|
50
|
+
GET /annotation/search?uri=$canvasUri
|
|
51
|
+
```
|
|
52
|
+
- **returns** `Annotation[]`
|
|
53
|
+
- `$canvasUri` is the URI for the canvas we want annotations for
|
|
54
|
+
- *example: https://aikon.enpc.fr/sas/annotation/search?uri=https://aikon.enpc.fr/aikon/iiif/v2/wit9_man11_anno165/canvas/c16.json#xywh=0,31,1865,1670 shows annotations for canvas https://aikon.enpc.fr/aikon/iiif/v2/wit9_man11_anno165/canvas/c16.json#xywh=0,31,1865,1670*
|
|
55
|
+
|
|
56
|
+
#### List all indexed IIIF manifests
|
|
57
|
+
```
|
|
58
|
+
GET /manifests
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **returns**
|
|
62
|
+
```js
|
|
63
|
+
{
|
|
64
|
+
"@type": "sc:Collection",
|
|
65
|
+
"@id": "http://annotation//collection/managed.json",
|
|
66
|
+
"label": "string",
|
|
67
|
+
"@context": "http://iiif.io/api/presentation/2/context.json",
|
|
68
|
+
"members": [
|
|
69
|
+
{
|
|
70
|
+
"@type": "sc:Manifest",
|
|
71
|
+
"@id": "URI"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Create/Update data
|
|
78
|
+
|
|
79
|
+
#### Populate / Import an AnnotationList into SAS
|
|
80
|
+
```
|
|
81
|
+
POST /annotation/populate
|
|
82
|
+
```
|
|
83
|
+
- POST body: the AnnotationList to import can be passed dirctly or as an URI to the AnnotationList (see [SAS source code](https://github.com/glenrobson/SimpleAnnotationServer/blob/dc7c8c6de9f4693c678643db2a996a49eebfcbb0/src/main/java/uk/org/llgc/annotation/store/Populate.java#L46)):
|
|
84
|
+
- `{ "uri": $annotationListUri }`, where `$annotationListUri` is an URI pointing to a IIIF annotations list
|
|
85
|
+
- IIIF AnnotationList
|
|
86
|
+
|
|
87
|
+
#### Create an annotation
|
|
88
|
+
```
|
|
89
|
+
POST /annotation/create
|
|
90
|
+
```
|
|
91
|
+
- POST body: IIIF Annotation
|
|
92
|
+
|
|
93
|
+
#### Update an annotation
|
|
94
|
+
```
|
|
95
|
+
POST /annotation/update
|
|
96
|
+
```
|
|
97
|
+
- POST body: IIIF Annotation
|
|
98
|
+
- the annotation's `@id` should point to an annotation that exists in the store
|
|
99
|
+
|
|
100
|
+
#### Index a new manifest
|
|
101
|
+
```
|
|
102
|
+
POST /manifests
|
|
103
|
+
```
|
|
104
|
+
- POST body: IIIF Manifest
|
|
105
|
+
|
|
106
|
+
### Delete data
|
|
107
|
+
|
|
108
|
+
#### Delete an annotation
|
|
109
|
+
```
|
|
110
|
+
DELETE /annotation/destroy/?uri=$annotationUri
|
|
111
|
+
```
|
|
112
|
+
- `$annotationUri` is the `@id` of the annotation to delete
|
|
113
|
+
|
|
114
|
+
#### Unindex a manifest
|
|
115
|
+
```
|
|
116
|
+
DELETE /manifests/$manifestId
|
|
117
|
+
```
|
|
118
|
+
- `$manifestId` is the short manifest identifier
|
|
119
|
+
- actually I'm not 100% sure it is implemented by SAS but we [would need it](https://github.com/Aikon-platform/aikon/blob/cc8430c52e205e6a1c04c4ae84f69126fb5a3bda/front/app/webapp/utils/iiif/annotation.py#L769)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# WRITING A MIRADOR PLUGIN
|
|
2
|
+
|
|
3
|
+
To integrate an annotation server with Mirador, we need to write a Mirador plugin that that interfaces Mirador with the annotation server.
|
|
4
|
+
|
|
5
|
+
Writing a mirador plugin is actually really simple: see the [annotot endpoint plugin](https://github.com/ProjectMirador/mirador-annotot-endpoint-plugin)
|
|
6
|
+
|
|
7
|
+
SAS apparently uses a [custom version of Mirador 2](https://github.com/glenrobson/SimpleAnnotationServer/tree/master/src/main/webapp/mirador-2.6.1)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Sources
|
|
12
|
+
|
|
13
|
+
- [Mirador docs: plugins](https://github.com/ProjectMirador/mirador/wiki/Mirador-3-plugins)
|
|
14
|
+
- [Mirador docs: writing a plugin](https://github.com/ProjectMirador/mirador/wiki/Creating-a-Mirador-4-plugin)
|
|
15
|
+
- [Mirador annoto endpoint](https://github.com/ProjectMirador/mirador-annotot-endpoint-plugin/tree/master)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install and use a Mirador plugin
|
|
20
|
+
|
|
21
|
+
1. npm-install the plugin you want to use:
|
|
22
|
+
```bash
|
|
23
|
+
npm install mirador-image-tools --save
|
|
24
|
+
```
|
|
25
|
+
2. when instanciating Mirador, add the plugin to the `plugins` array:
|
|
26
|
+
```js
|
|
27
|
+
import miradorImageToolsPlugin from 'mirador-image-tools/es/plugins/miradorImageToolsPlugin.js';
|
|
28
|
+
|
|
29
|
+
const config = {
|
|
30
|
+
// your Mirador config
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const plugins = [
|
|
34
|
+
...miradorImageToolsPlugin
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
Mirador.viewer(config, plugins);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Writing a plugin
|
|
43
|
+
|
|
44
|
+
### General architecture
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
root/
|
|
48
|
+
|_src
|
|
49
|
+
|_index.js // exports the plugin
|
|
50
|
+
|_components/ // folder containing all the react components that comprise the plugin
|
|
51
|
+
|_pluginComponent.js // the react files.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Plugin config
|
|
55
|
+
|
|
56
|
+
The plugin's configuration in `index.js` defines how to inject the plugin in Mirador:
|
|
57
|
+
```js
|
|
58
|
+
const plugin = {
|
|
59
|
+
target: 'WorkspaceControlPanelButtons', // where to inject the plugin in the Mirador architecture
|
|
60
|
+
mode: 'wrap', // how to inject. "add"/"wrap"
|
|
61
|
+
component: PluginComponent, // the plugin as a React component
|
|
62
|
+
// `connectOptions`, `mapStateToProps` and `mapDispatchToProps` connect the plugin to Mirador
|
|
63
|
+
connectOptions: additionalOptionsToPassToReduxConnect, // plugin extras
|
|
64
|
+
mapStateToProps: mapStateToProps, // React redux function that connects store data with react props
|
|
65
|
+
mapDispatchToProps: mapDispatchToProps, // React redux function that dispatches actions to the store
|
|
66
|
+
// `reducers` and `saga` manipulate state
|
|
67
|
+
reducers: {
|
|
68
|
+
pluginState: pluginStateReducer,
|
|
69
|
+
},
|
|
70
|
+
saga: saga
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`PluginComponent` is a React component that contains the entire plugin. Nested React components are allowed in `PluginComponent`.
|
|
75
|
+
|
|
76
|
+
About the `mode`:
|
|
77
|
+
- `mode=add` injects the component at designated areas of the Mirador app.
|
|
78
|
+
- `mode=wrap` overrides mirador behaviour.
|
|
79
|
+
- wrapping plugins can:
|
|
80
|
+
- replace the content of a Mirador coponent
|
|
81
|
+
- wrap a Mirador component with additional context
|
|
82
|
+
- to intercept and modify the attributes of a component
|
|
83
|
+
- all Mirador components can be wrapped
|
|
84
|
+
|
|
85
|
+
### Plugin components
|
|
86
|
+
|
|
87
|
+
A plugin component is a React component:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import React, { Component } from 'react';
|
|
91
|
+
|
|
92
|
+
export default function () {
|
|
93
|
+
return <h5>Custom metadata!</h5>;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
In the example above,
|
|
98
|
+
- if `mode=add`, the `PluginComponent` will will be added as a child of its `target`.
|
|
99
|
+
- if `mode=wrap`, the `PluginComponent` will replace the `target`.
|
|
100
|
+
|
|
101
|
+
When `mode = wrap`, the plugin component can modify its `target`. The plugin will receive the Mirador component that is being wrapped as an attribute => we can change the props of the component, surround it with extra context, change its behaviour...
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import React, { Component } from 'react';
|
|
105
|
+
|
|
106
|
+
export default function ({ TargetComponent, targetProps }) {
|
|
107
|
+
return <TargetComponent {...targetProps} />;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Annotot plugin
|
|
114
|
+
|
|
115
|
+
- [repo](https://github.com/ProjectMirador/mirador-annotot-endpoint-plugin/)
|
|
116
|
+
- [plugin component](https://github.com/ProjectMirador/mirador-annotot-endpoint-plugin/blob/master/src/plugins/miradorAnnototEndpointPlugin.js)
|
|
117
|
+
- [plugin config](https://github.com/ProjectMirador/mirador-annotot-endpoint-plugin/blob/42a04b93429b0a85dee50a3dbe4d6d5e3fb8046e/src/plugins/miradorAnnototEndpointPlugin.js#L65)
|
|
118
|
+
- [usage demo](https://github.com/ProjectMirador/mirador-annotot-endpoint-plugin/blob/master/demo/src/index.js)
|
|
119
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# AIKON NEEDS
|
|
2
|
+
|
|
3
|
+
Hre we desribe the functionnalities our annotation server should implement, and list which functionnalities are aldready implemented by SAS.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Reminders
|
|
8
|
+
|
|
9
|
+
- in Aikon, a Regions corresponds to a regions extraction on a document, and that Regions extrction corresponds to a IIIF manifest. 1 manifest => 1 regions extraction
|
|
10
|
+
- IIIF and SAS specific code in Aikon is stored in [front.app.webapp.utils.iiif](https://github.com/Aikon-platform/aikon/tree/main/front/app/webapp/utils/iiif)
|
|
11
|
+
- in AIKON, IIIF manifests aren't stored in the app. Instead, they are generated dynamically and served when needed.
|
|
12
|
+
- IIIF and SAS specific code in Aikon is stored in [front.app.webapp.utils.iiif](https://github.com/Aikon-platform/aikon/tree/main/front/app/webapp/utils/iiif)
|
|
13
|
+
- see [front.app.webapp.utils.iiif.manifests](https://github.com/Aikon-platform/aikon/blob/main/front/app/webapp/utils/iiif/manifest.py)
|
|
14
|
+
- *note: IIIF manifests are stored by SAS, because SAS needs the manifests. In the app however, we don't use the SAS stored manifests*
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Implemented by SAS
|
|
19
|
+
|
|
20
|
+
### Fetch data
|
|
21
|
+
|
|
22
|
+
- get all manifests stored in SAS
|
|
23
|
+
- SAS route: `GET /manifests`
|
|
24
|
+
- get all annotations / implement a IIIF search API.
|
|
25
|
+
- SAS route: `GET /search-api/$manifestId/search`
|
|
26
|
+
- if we don't implement a full search API, we can use this route to return all annotations for a manifest
|
|
27
|
+
- get all annotations for a single canvas
|
|
28
|
+
- SAS route: `GET /annotation/search/?uri=$canvasUri`, where `$canvasUri` is the URI of the canvas for which we want the annotatione
|
|
29
|
+
|
|
30
|
+
### Create / update data
|
|
31
|
+
|
|
32
|
+
- import a new IIIF manifest
|
|
33
|
+
- SAS route: `POST /manifests`, where the body is the new IIIF manifest
|
|
34
|
+
- import an AnnotationList
|
|
35
|
+
- SAS route: `POST /annotation/populate`, where the body is either an URI pointing to an AnnotationList, or the AnnotationList itself
|
|
36
|
+
|
|
37
|
+
### Delete data
|
|
38
|
+
|
|
39
|
+
- delete a manifest (actually not 100% sure that this is implemented by SAS)
|
|
40
|
+
- sas route: `DELETE /manifests`
|
|
41
|
+
- delete an annotation
|
|
42
|
+
- sas route: `DELETE /annotation/destroy/?uri=$annotationUri` where `$annotationUri` is the ID of the annotation to delete
|
|
43
|
+
|
|
44
|
+
### Provided by SAS but not currently used in AIKON:
|
|
45
|
+
|
|
46
|
+
- `POST /annotation/create`: crate a single annotation
|
|
47
|
+
- `POST /annotation/update`: update a single annotation
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Not implemented by SAS
|
|
52
|
+
|
|
53
|
+
### Fetch data
|
|
54
|
+
|
|
55
|
+
- get all annotations for a single manifest ([code](https://github.com/Aikon-platform/aikon/blob/cc8430c52e205e6a1c04c4ae84f69126fb5a3bda/front/app/webapp/utils/iiif/annotation.py#L32)).
|
|
56
|
+
- this behaviour is implemented by SAS but results are paginated which requires to run several queries
|
|
57
|
+
- get the total number of annotations in a single manifest ([code](https://github.com/Aikon-platform/aikon/blob/cc8430c52e205e6a1c04c4ae84f69126fb5a3bda/front/app/webapp/utils/iiif/annotation.py#L648))
|
|
58
|
+
- get all annotations for a specific canvas
|
|
59
|
+
- get all annotations for a range of canvases
|
|
60
|
+
- have several "export format" for an annotation:
|
|
61
|
+
- IIIF annotation of course
|
|
62
|
+
```
|
|
63
|
+
{
|
|
64
|
+
"@id" : "http://aikon.enpc.fr/sas/annotation/wit69_man69_anno134_c179_23f885ed66914139ab7d67d22f8f8f46",
|
|
65
|
+
"@type" : "oa:Annotation",
|
|
66
|
+
"dcterms:created" : "2025-03-03T14:40:57",
|
|
67
|
+
"dcterms:modified" : "2025-03-03T14:40:57",
|
|
68
|
+
"resource" : {
|
|
69
|
+
"@type" : "dctypes:Text",
|
|
70
|
+
"format" : "text/html",
|
|
71
|
+
"chars" : "<p></p>",
|
|
72
|
+
"https://aikon.enpc.fr/sas/full_text" : "",
|
|
73
|
+
"https://iscd.huma-num.fr/sas/full_text" : ""
|
|
74
|
+
},
|
|
75
|
+
"on" : "https://aikon.enpc.fr/aikon/iiif/v2/wit69_man69_anno134/canvas/c179.json#xywh=52,1221,891,54",
|
|
76
|
+
"motivation" : [ "oa:tagging", "oa:commenting" ],
|
|
77
|
+
"@context" : "http://iiif.io/* TLSv1.2 (IN), TLS header, Supplemental data (23):
|
|
78
|
+
api/presentation/2/context.json",
|
|
79
|
+
"label" : ""
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
- standardized metadata (used in application for svelte) (e.g. `https://iscd.huma-num.fr/vhs/witness/2339/regions/canvas`)
|
|
83
|
+
```
|
|
84
|
+
r_annos[canvas][aid] = {
|
|
85
|
+
"id": aid,
|
|
86
|
+
"ref": f"{img}_{xywh}",
|
|
87
|
+
"class": "Region",
|
|
88
|
+
"type": get_name("Regions"),
|
|
89
|
+
"title": region_title(canvas, xywh),
|
|
90
|
+
"url": gen_iiif_url(img, res=f"{xywh}/full/0"),
|
|
91
|
+
"canvas": canvas,
|
|
92
|
+
"xywh": xywh.split(","),
|
|
93
|
+
"img": img,
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
- only ids
|
|
97
|
+
```
|
|
98
|
+
manifest_annotations.extend(
|
|
99
|
+
annotation["@id"] for annotation in annotations["resources"]
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
- API url list (e.g. [`https://iscd.huma-num.fr/vhs/witness/2339/regions/canvas`](https://iscd.huma-num.fr/vhs/wit249_man249_anno249/list/))
|
|
103
|
+
```
|
|
104
|
+
"wit249_man249_0021_695,1020,123,214": "https://iscd.huma-num.fr/iiif/2/wit249_man249_0021.jpg/695,1020,123,214/full/0/default.jpg",
|
|
105
|
+
"wit249_man249_0021_880,1032,421,135": "https://iscd.huma-num.fr/iiif/2/wit249_man249_0021.jpg/880,1032,421,135/full/0/default.jpg",
|
|
106
|
+
"wit249_man249_0021_167,1282,505,770": "https://iscd.huma-num.fr/iiif/2/wit249_man249_0021.jpg/167,1282,505,770/full/0/default.jpg",
|
|
107
|
+
"wit249_man249_0021_308,1179,220,236": "https://iscd.huma-num.fr/iiif/2/wit249_man249_0021.jpg/308,1179,220,236/full/0/default.jpg",
|
|
108
|
+
"wit249_man249_0021_207,1013,468,149": "https://iscd.huma-num.fr/iiif/2/wit249_man249_0021.jpg/207,1013,468,149/full/0/default.jpg"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Create / update data
|
|
112
|
+
|
|
113
|
+
- index all annotations for a manifest in 1 query ([code](https://github.com/Aikon-platform/aikon/blob/cc8430c52e205e6a1c04c4ae84f69126fb5a3bda/front/app/webapp/utils/iiif/annotation.py#L197)).
|
|
114
|
+
|
|
115
|
+
### Delete data
|
|
116
|
+
|
|
117
|
+
- unindex a manifest ([code](https://github.com/Aikon-platform/aikon/blob/cc8430c52e205e6a1c04c4ae84f69126fb5a3bda/front/app/webapp/utils/iiif/annotation.py#L769))
|
|
118
|
+
- remove all annotations for a single manifest in 1 query ([code](https://github.com/Aikon-platform/aikon/blob/cc8430c52e205e6a1c04c4ae84f69126fb5a3bda/front/app/webapp/utils/iiif/annotation.py#L798)). currently, we need to
|
|
119
|
+
- loop over each canvas in a manifest
|
|
120
|
+
- loop over each annotation in the canvas
|
|
121
|
+
- delete that annotation in an HTTP request => tons of HTTP requests
|
|
122
|
+
|
|
123
|
+
### Other
|
|
124
|
+
|
|
125
|
+
- annotations should be ordered by their position on the page (or have a method that returns annotations ordered)
|
|
126
|
+
- store rectangular annotations (bounding boxes) as well as polygonal annotations
|
|
127
|
+
- canvas number management:
|
|
128
|
+
- annotation should have their canvas number as standard metadata (for now we need to parse `canvas = anno["on"].split("/canvas/c")[1].split(".json")[0]` 😰)
|
|
129
|
+
- make annotations ordered not alphabetically (137 arriving before 14) but by canvas order
|
|
130
|
+
- technically, this means that, when saving an annotation, you need to:
|
|
131
|
+
- fetch the manifest of the `annotation.on`
|
|
132
|
+
- index it (minimally, as a manifest URL + ordered array of canvases)
|
|
133
|
+
- extract the proper canvas number for the current annotation
|
|
134
|
+
- save in the annotation the canvas number
|
|
135
|
+
- Create a pipeline to import SAS annotation inside new server easily
|
|
136
|
+
|
|
137
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import json from "@eslint/json";
|
|
4
|
+
import css from "@eslint/css";
|
|
5
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
6
|
+
import stylistic from "@stylistic/eslint-plugin"
|
|
7
|
+
|
|
8
|
+
/** https://eslint.org/docs/latest/use/configure/rules */
|
|
9
|
+
export default defineConfig([
|
|
10
|
+
{
|
|
11
|
+
files: ["**/*.{js,mjs,cjs}"],
|
|
12
|
+
plugins: {
|
|
13
|
+
"js": js,
|
|
14
|
+
"@stylistic": stylistic
|
|
15
|
+
},
|
|
16
|
+
extends: ["js/recommended"],
|
|
17
|
+
languageOptions: { globals: {...globals.browser, ...globals.node} },
|
|
18
|
+
rules: {
|
|
19
|
+
"no-unused-vars": "off",
|
|
20
|
+
"quotes": ["error", "double"],
|
|
21
|
+
"@stylistic/indent": ["error", 2],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
|
|
25
|
+
{ files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] },
|
|
26
|
+
globalIgnores(["package-lock.json"]),
|
|
27
|
+
]);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef Config
|
|
7
|
+
* @type {object}
|
|
8
|
+
* @property {string} connString
|
|
9
|
+
* @property {dbName} dbName
|
|
10
|
+
* @property {migrationsDir} migrationsDir
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// path to dirctory of curent file
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* generate a migrate-mongo config file based on options defined in `mongoConfig`.
|
|
18
|
+
* the object returned here is a copy of what's generated by `migrate-mongo init`.
|
|
19
|
+
* @param {Config} config */
|
|
20
|
+
export default (config) => ({
|
|
21
|
+
mongodb: {
|
|
22
|
+
url: config.connString,
|
|
23
|
+
|
|
24
|
+
databaseName: config.dbName,
|
|
25
|
+
|
|
26
|
+
options: {
|
|
27
|
+
useNewUrlParser: true, // removes a deprecation warning when connecting
|
|
28
|
+
useUnifiedTopology: true, // removes a deprecating warning when connecting
|
|
29
|
+
// connectTimeoutMS: 3600000, // increase connection timeout to 1 hour
|
|
30
|
+
// socketTimeoutMS: 3600000, // increase socket timeout to 1 hour
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// The migrations dir, can be an relative or absolute path. Only edit this when really necessary.
|
|
35
|
+
migrationsDir: path.join(path.resolve(__dirname), "migrationScripts"),
|
|
36
|
+
|
|
37
|
+
// The mongodb collection where the applied changes are stored. Only edit this when really necessary.
|
|
38
|
+
changelogCollectionName: "changelog",
|
|
39
|
+
|
|
40
|
+
// The mongodb collection where the lock will be created.
|
|
41
|
+
lockCollectionName: "changelog_lock",
|
|
42
|
+
|
|
43
|
+
// The value in seconds for the TTL index that will be used for the lock. Value of 0 will disable the feature.
|
|
44
|
+
lockTtl: 0,
|
|
45
|
+
|
|
46
|
+
// The file extension to create migrations and search for in migration dir
|
|
47
|
+
migrationFileExtension: ".js",
|
|
48
|
+
|
|
49
|
+
// Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine
|
|
50
|
+
// if the file should be run. Requires that scripts are coded to be run multiple times.
|
|
51
|
+
useFileHash: false,
|
|
52
|
+
|
|
53
|
+
// Don't change this, unless you know what you're doing
|
|
54
|
+
//NOTE: default value is 'commonjs'
|
|
55
|
+
moduleSystem: "esm",
|
|
56
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utility functions to create/remove an index
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef IndexSpecType
|
|
7
|
+
* @type {object}
|
|
8
|
+
* @property {string} indexName - the name of the index we're creating
|
|
9
|
+
* @property {any} [prop] - any other key-value pairs are allowed
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** @typedef {import("mongodb").IndexSpecification} IndexSpecificationType */
|
|
13
|
+
/** @typedef {import("mongodb").Db} DbType */
|
|
14
|
+
/** @typedef {import("mongodb").CreateIndexesOptions} CreateIndexesOptionsType */
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {CreateIndexesOptionsType} indexOptions - indexOptions.name MUST be defined !
|
|
19
|
+
*/
|
|
20
|
+
const validateIndexOptions = (indexOptions) => {
|
|
21
|
+
if ( indexOptions.name == null ) {
|
|
22
|
+
throw new Error("indexOptions.name must be defined !")
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @param {DbType} db
|
|
29
|
+
* @param {string} collectionName
|
|
30
|
+
* @param {IndexSpecificationType} indexSpec
|
|
31
|
+
* @param {CreateIndexesOptionsType} indexOptions - indexOptions.name MUST be defined !
|
|
32
|
+
*/
|
|
33
|
+
async function createIndex(db, collectionName, indexSpec, indexOptions) {
|
|
34
|
+
validateIndexOptions(indexOptions);
|
|
35
|
+
const collection = db.collection(collectionName);
|
|
36
|
+
const result = await collection.createIndex(indexSpec, indexOptions);
|
|
37
|
+
console.log("created index:", result);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {DbType} db
|
|
42
|
+
* @param {string} collectionName
|
|
43
|
+
* @param {CreateIndexesOptionsType} indexOptions - indexOptions.name MUST be defined !
|
|
44
|
+
*/
|
|
45
|
+
async function removeIndex(db, collectionName, indexOptions) {
|
|
46
|
+
validateIndexOptions(indexOptions);
|
|
47
|
+
const collection = db.collection(collectionName);
|
|
48
|
+
const result = await collection.dropIndex(indexOptions.name);
|
|
49
|
+
console.log("dropped index:", result);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
createIndex,
|
|
54
|
+
removeIndex
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* create the collections of the database (without schema)
|
|
3
|
+
* - annotations3 : annotations following the w3c Web Annotations standard (used by IIIF 3.0 presentation API)
|
|
4
|
+
* - annotations2 : annotations following the Open Annotations standard (used by IIIF 2.1 presentation API)
|
|
5
|
+
* - manifests3 : IIIF manifests following the IIIF 3.0 presentation API
|
|
6
|
+
* - manifests2 : IIIF manifests following the IIIF 2.1 (and 2.0) presentation API
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const collectionNames = [
|
|
10
|
+
"annotations3",
|
|
11
|
+
"annotations2",
|
|
12
|
+
"manifests3",
|
|
13
|
+
"manifests2"
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('mongodb').Db} db
|
|
18
|
+
* @param {import('mongodb').MongoClient} client
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
export const up = async (db, client) => {
|
|
22
|
+
// See https://github.com/seppevs/migrate-mongo/#creating-a-new-migration-script
|
|
23
|
+
collectionNames.forEach((colName) => {
|
|
24
|
+
db.createCollection(colName);
|
|
25
|
+
})
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {import('mongodb').Db} db
|
|
30
|
+
* @param {import('mongodb').MongoClient} client
|
|
31
|
+
* @returns {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
export const down = async (db, client) => {
|
|
34
|
+
// Example:
|
|
35
|
+
// await db.collection('albums').updateOne({artist: 'The Beatles'}, {$set: {blacklisted: false}});
|
|
36
|
+
collectionNames.forEach(async (colName) => {
|
|
37
|
+
const collection = db.collection(colName);
|
|
38
|
+
await collection.drop();
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* create a text index in collection `annotations2` on annotation targets;
|
|
3
|
+
* indexes key is `on.full` that describes the target canvases for each annotation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const
|
|
7
|
+
colName = "annotations2",
|
|
8
|
+
indexSpec = { "on.full": 1 },
|
|
9
|
+
indexOptions = { name: "canvasIdIndex" };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {import('mongodb').Db} db
|
|
13
|
+
* @param {import('mongodb').MongoClient} client
|
|
14
|
+
* @returns {Promise<void>}
|
|
15
|
+
*/
|
|
16
|
+
export const up = async (db, client) => {
|
|
17
|
+
const collection = db.collection(colName);
|
|
18
|
+
const result = await collection.createIndex(indexSpec, indexOptions);
|
|
19
|
+
console.log("created index:", result);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {import('mongodb').Db} db
|
|
24
|
+
* @param {import('mongodb').MongoClient} client
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
export const down = async (db, client) => {
|
|
28
|
+
const collection = db.collection(colName);
|
|
29
|
+
const result = await collection.dropIndex(indexOptions.name);
|
|
30
|
+
console.log("dropped index:", result);
|
|
31
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* define the validation schema on collection `annotations2`.
|
|
3
|
+
*/
|
|
4
|
+
import build from "#src/app.js"
|
|
5
|
+
|
|
6
|
+
// const inspect = (theObj) => util.inspect(theObj, {showHidden: false, depth: null, colors: true});
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param db {import('mongodb').Db}
|
|
10
|
+
* @param client {import('mongodb').MongoClient}
|
|
11
|
+
* @returns {Promise<void>}
|
|
12
|
+
*/
|
|
13
|
+
export const up = async (db, client) => {
|
|
14
|
+
const
|
|
15
|
+
fastify = await build(),
|
|
16
|
+
fastifySchema = fastify.schemasPresentation2.getSchema("annotation"),
|
|
17
|
+
schema = fastify.schemasResolver(fastifySchema),
|
|
18
|
+
commandDoc = {
|
|
19
|
+
collMod: "annotations2",
|
|
20
|
+
validator: { $jsonSchema: schema }
|
|
21
|
+
},
|
|
22
|
+
r = await db.command(commandDoc);
|
|
23
|
+
|
|
24
|
+
if ( r.ok !== 1 ) {
|
|
25
|
+
throw new Error(`command failed with error: ${r}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param db {import('mongodb').Db}
|
|
31
|
+
* @param client {import('mongodb').MongoClient}
|
|
32
|
+
* @returns {Promise<void>}
|
|
33
|
+
*/
|
|
34
|
+
export const down = async (db, client) => {
|
|
35
|
+
const r = await db.command({
|
|
36
|
+
collMod: "annotations2",
|
|
37
|
+
validator: {}
|
|
38
|
+
});
|
|
39
|
+
if ( r.ok !== 1 ) {
|
|
40
|
+
throw new Error(`command failed with error: ${r}`);
|
|
41
|
+
}
|
|
42
|
+
};
|