aiiinotate 0.6.2 → 0.8.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/README.md +57 -26
- package/cli/import.js +49 -81
- package/cli/index.js +2 -17
- package/cli/migrate.js +2 -1
- package/cli/serve.js +3 -3
- package/cli/utils/fastifyClient.js +74 -0
- package/cli/utils/io.js +5 -36
- package/cli/utils/mongoClient.js +4 -2
- package/cli/utils/progressbar.js +86 -0
- package/config/.env.template +7 -0
- package/docs/cli.md +95 -0
- package/docs/endpoints.md +45 -20
- package/package.json +8 -6
- package/scripts/run.sh +36 -0
- package/src/app.js +32 -29
- package/src/constants.js +61 -0
- package/src/data/annotations/annotations2.js +88 -45
- package/src/data/annotations/routes.js +13 -10
- package/src/data/annotations/routes.test.js +30 -11
- package/src/data/collectionAbstract.js +11 -2
- package/src/data/manifests/manifests2.js +7 -4
- package/src/data/routes.test.js +0 -45
- package/src/db/index.js +6 -4
- package/src/fixtures/generate.js +2 -1
- package/src/fixtures/utils.js +3 -1
- package/src/schemas/schemasBase.js +3 -1
- package/src/schemas/schemasPresentation2.js +18 -20
- package/src/schemas/schemasPresentation3.js +3 -8
- package/src/schemas/schemasRoutes.js +3 -1
- package/src/server.js +4 -3
- package/src/types.js +1 -1
- package/src/utils/iiif2Utils.js +8 -5
- package/src/utils/logger.js +118 -0
- package/src/utils/routeUtils.js +5 -2
- package/src/utils/svg.js +2 -1
- package/src/utils/utils.js +13 -7
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ aiiinotate is a fast and lightweight annotation server for IIIF. It relies on `n
|
|
|
6
6
|
|
|
7
7
|
## PROD USAGE
|
|
8
8
|
|
|
9
|
-
### Install
|
|
9
|
+
### Install
|
|
10
10
|
|
|
11
11
|
1. **Install mongodb**.
|
|
12
12
|
- see [dev installation script for help](./scripts/setup_mongodb.sh)
|
|
@@ -17,9 +17,37 @@ aiiinotate is a fast and lightweight annotation server for IIIF. It relies on `n
|
|
|
17
17
|
npm install aiiinotate
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
###
|
|
20
|
+
### Env definition
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
#### Basic definition
|
|
23
|
+
|
|
24
|
+
Copy [`config/.env.template`](./config/.env.template) to `.env` and edit it.
|
|
25
|
+
|
|
26
|
+
#### Runtime env sourcing
|
|
27
|
+
|
|
28
|
+
Once the package is installed, it must access variables from the .env file. However, running `aiiinotate <commands>` creates a subscript which means **you can't `source` an `.env` file**.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# THIS WILL FAIL: `aiiinotate` is executed in a subscript that doesn't inherit from the variables fetched in `source`.
|
|
32
|
+
source /path/to/.env && aiiinotate <command>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### The solutions: do either:
|
|
36
|
+
1. use `dotenvx` to inject variables:
|
|
37
|
+
```bash
|
|
38
|
+
npx dotenvx run -f /path/to/.env -- aiiinotate <command>
|
|
39
|
+
```
|
|
40
|
+
2. manually export variables:
|
|
41
|
+
```bash
|
|
42
|
+
set -a
|
|
43
|
+
source /path/to/.env
|
|
44
|
+
set +a
|
|
45
|
+
aiiinotate <command>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For clarity, we omit env sourcing from the below commands.
|
|
49
|
+
|
|
50
|
+
### Setup the app
|
|
23
51
|
|
|
24
52
|
1. **Start `mongod`**
|
|
25
53
|
|
|
@@ -30,38 +58,41 @@ sudo systemctl start mongod
|
|
|
30
58
|
2. **Create and configure the database**
|
|
31
59
|
|
|
32
60
|
```bash
|
|
33
|
-
aiiinotate
|
|
61
|
+
aiiinotate migrate apply
|
|
34
62
|
```
|
|
35
63
|
|
|
36
64
|
### Usage
|
|
37
65
|
|
|
38
66
|
All commands are accessible through a CLI (`./src/cli`).
|
|
39
67
|
|
|
40
|
-
#### Run the app
|
|
68
|
+
#### Run the app
|
|
41
69
|
|
|
42
70
|
```bash
|
|
43
|
-
aiiinotate
|
|
44
|
-
# or
|
|
45
|
-
aiiinotate --env <path-to-your-env-file> -- serve dev
|
|
71
|
+
aiiinotate serve prod
|
|
46
72
|
```
|
|
47
73
|
|
|
48
|
-
#### Run
|
|
74
|
+
#### Run the CLI
|
|
49
75
|
|
|
50
76
|
The base command is:
|
|
51
77
|
|
|
52
78
|
```bash
|
|
53
|
-
aiiinotate --
|
|
79
|
+
aiiinotate -- <command>
|
|
54
80
|
```
|
|
55
81
|
|
|
56
82
|
It will give full access to the CLI interface of Aiiinotate. Run `aiiinotate --help` for more info.
|
|
57
83
|
|
|
58
|
-
|
|
84
|
+
For more information, see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/main/docs/cli.md).
|
|
85
|
+
|
|
86
|
+
#### Import data
|
|
87
|
+
|
|
88
|
+
See [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/main/docs/cli.md).
|
|
89
|
+
|
|
59
90
|
|
|
60
91
|
---
|
|
61
92
|
|
|
62
93
|
## DEV USAGE
|
|
63
94
|
|
|
64
|
-
### Install
|
|
95
|
+
### Install
|
|
65
96
|
|
|
66
97
|
```bash
|
|
67
98
|
# clone the repo
|
|
@@ -84,15 +115,15 @@ npm i
|
|
|
84
115
|
|
|
85
116
|
After installing, some setup must be done
|
|
86
117
|
|
|
87
|
-
|
|
118
|
+
1. **Setup your `.env`** file after [`config/.env.template`](./config/.env.template) and place it at `./config/.env`.
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
2. **Start `mongod`**
|
|
90
121
|
|
|
91
122
|
```bash
|
|
92
123
|
sudo systemctl start mongod
|
|
93
124
|
```
|
|
94
125
|
|
|
95
|
-
|
|
126
|
+
3. **Configure the database**
|
|
96
127
|
|
|
97
128
|
```bash
|
|
98
129
|
npm run migrate apply
|
|
@@ -112,32 +143,32 @@ npm run dev
|
|
|
112
143
|
npm run prod
|
|
113
144
|
```
|
|
114
145
|
|
|
115
|
-
- **Test the app
|
|
146
|
+
- **Test the app**. NOTE: the tests will probably fail if you set the env variable `AIIINOTATE_STRICT_MODE` to `true`.
|
|
116
147
|
|
|
117
148
|
```bash
|
|
118
149
|
npm run test
|
|
119
150
|
```
|
|
120
151
|
|
|
121
|
-
- **Run the CLI
|
|
152
|
+
- **Run the CLI**. (see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/cli.md) for more info)
|
|
153
|
+
|
|
122
154
|
|
|
123
155
|
```bash
|
|
124
156
|
npm cli
|
|
125
157
|
```
|
|
126
158
|
|
|
127
|
-
- **Process migrations
|
|
159
|
+
- **Process migrations**. (see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/dev/docs/cli.md) for more info)
|
|
128
160
|
|
|
129
161
|
```bash
|
|
130
|
-
#
|
|
131
|
-
npm run migrate
|
|
162
|
+
# NOTE: the `--` is necessary !
|
|
163
|
+
npm run migrate -- <command> <arguments?>
|
|
164
|
+
```
|
|
132
165
|
|
|
133
|
-
# apply all pending migrations
|
|
134
|
-
npm run migrate apply
|
|
135
166
|
|
|
136
|
-
|
|
137
|
-
npm run migrate revert
|
|
167
|
+
- **Import data**. (see [the CLI docs](https://github.com/Aikon-platform/aiiinotate/blob/main/docs/cli.md) for more info)
|
|
138
168
|
|
|
139
|
-
|
|
140
|
-
|
|
169
|
+
```bash
|
|
170
|
+
# NOTE: the `--` is necessary !
|
|
171
|
+
npm run cli -- import <arguments>
|
|
141
172
|
```
|
|
142
173
|
|
|
143
174
|
---
|
package/cli/import.js
CHANGED
|
@@ -1,40 +1,17 @@
|
|
|
1
1
|
import { Command, Option, Argument } from "commander";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
3
|
+
import { fileRead, parseImportInputFile } from "#cli/utils/io.js";
|
|
4
|
+
import FastifyClient from "#cli/utils/fastifyClient.js";
|
|
5
|
+
import ProgressBar from "#cli/utils/progressbar.js";
|
|
6
|
+
import logger from "#utils/logger.js";
|
|
7
|
+
import { inspectObj } from "#utils/utils.js";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// allowed imports
|
|
11
|
-
const importTypes = [
|
|
12
|
-
"annotation", // import a single annotation
|
|
13
|
-
"annotation-list", // import a IIIF 2.x annotationList
|
|
14
|
-
"annotation-page", // import a IIIF 3.x annotationPage
|
|
15
|
-
"manifest", // import a single manifest
|
|
16
|
-
// "annotation-array", // import a JSON array of IIIF annotations
|
|
17
|
-
// "manifest-array" // import a json array of manifests
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
// allowed import types per IIIF version
|
|
21
|
-
const allowedImportTypes = {
|
|
22
|
-
2: ["annotation", "annotation-list", "manifest"],
|
|
23
|
-
3: ["annotation", "annotation-page", "manifest"]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const checkAllowedImportType = (iiifVersion, dataType) => {
|
|
27
|
-
if (
|
|
28
|
-
! allowedImportTypes[iiifVersion].includes(dataType)
|
|
29
|
-
) {
|
|
30
|
-
console.error(`${checkAllowedImportType.name}: forbidden import type '${dataType}' for IIIF version '${iiifVersion}'. allowed import types are: ${allowedImportTypes[iiifVersion]}`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
};
|
|
9
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
33
10
|
|
|
34
|
-
|
|
11
|
+
////////////////////////////////////////
|
|
35
12
|
|
|
36
13
|
const notImplementedExit = (method) => {
|
|
37
|
-
|
|
14
|
+
logger.error(`\n\nERROR: import is not implemented '${method}'`);
|
|
38
15
|
process.exit(1);
|
|
39
16
|
}
|
|
40
17
|
|
|
@@ -42,28 +19,42 @@ const parseNumber = (x) => Number(x);
|
|
|
42
19
|
|
|
43
20
|
////////////////////////////////////////
|
|
44
21
|
|
|
45
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @param {FastifyInstanceType} fastifyClient
|
|
24
|
+
* @param {string[]} fileArr - array of full paths to annotationLists to insert.
|
|
25
|
+
*/
|
|
26
|
+
async function importAnnotationPage(fastifyClient, fileArr) {
|
|
46
27
|
notImplementedExit(`${importAnnotationPage.name} is not implemented`)
|
|
47
28
|
}
|
|
48
29
|
|
|
49
30
|
/**
|
|
50
|
-
*
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {string[]} fileArr
|
|
53
|
-
* @param {2|3} iiifVersion
|
|
31
|
+
* @param {FastifyClient} fastifyClient
|
|
32
|
+
* @param {string[]} fileArr - array of full paths to annotationLists to insert.
|
|
54
33
|
*/
|
|
55
|
-
async function importAnnotationList(
|
|
56
|
-
//
|
|
57
|
-
|
|
34
|
+
async function importAnnotationList(fastifyClient, fileArr) {
|
|
35
|
+
// fileArr = fileArr.slice(0,10)
|
|
36
|
+
|
|
37
|
+
const pb = new ProgressBar({ desc: "importing annotations", total: fileArr.length});
|
|
38
|
+
const importErrors = [];
|
|
58
39
|
let totalImports = 0
|
|
59
40
|
|
|
60
|
-
for (const file of fileArr) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
41
|
+
for ( const [i, file] of fileArr.entries() ) {
|
|
42
|
+
const
|
|
43
|
+
annotationList = JSON.parse(fileRead(file)),
|
|
44
|
+
[statusCode, resultPromise] = await fastifyClient.importAnnotationList(annotationList),
|
|
45
|
+
result = await resultPromise;
|
|
46
|
+
if ( statusCode <= 299 ) {
|
|
47
|
+
totalImports += result.insertedCount;
|
|
48
|
+
} else {
|
|
49
|
+
importErrors.push(file);
|
|
50
|
+
}
|
|
51
|
+
pb.update(i)
|
|
64
52
|
}
|
|
65
53
|
|
|
66
|
-
|
|
54
|
+
if ( importErrors.length ) {
|
|
55
|
+
logger.info(`There were problems importing annotations from the following ${importErrors.length} annotation lists: ${inspectObj(importErrors, -1)}`)
|
|
56
|
+
}
|
|
57
|
+
logger.info(`Imported ${totalImports} annotations into Aiiinotate !`);
|
|
67
58
|
return
|
|
68
59
|
}
|
|
69
60
|
|
|
@@ -72,51 +63,33 @@ async function importAnnotationList(annotations2, fileArr, iiifVersion) {
|
|
|
72
63
|
/**
|
|
73
64
|
* run the cli
|
|
74
65
|
* @param {import('commander').Command} command
|
|
75
|
-
* @param {string} dataType: one of importTypes
|
|
76
66
|
* @param {object} options
|
|
77
67
|
*/
|
|
78
|
-
async function action(command,
|
|
79
|
-
const mongoClient = loadMongoClient();
|
|
68
|
+
async function action(command, options) {
|
|
80
69
|
|
|
81
70
|
/** @type {2 | 3} */
|
|
82
71
|
const iiifVersion = options.iiifVersion;
|
|
83
72
|
/** @type {string[]} */
|
|
84
|
-
const
|
|
85
|
-
/** @type {boolean} */
|
|
86
|
-
const listFiles = options.listFiles;
|
|
87
|
-
|
|
88
|
-
checkAllowedImportType(iiifVersion, dataType);
|
|
89
|
-
|
|
90
|
-
const filesToProcess = getFilesToProcess(files, listFiles);
|
|
73
|
+
const inputFile = options.file;
|
|
91
74
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
mongoClient.db()
|
|
95
|
-
);
|
|
75
|
+
const fastifyClient = new FastifyClient();
|
|
76
|
+
await fastifyClient.build();
|
|
96
77
|
|
|
97
78
|
// run
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
notImplementedExit(dataType);
|
|
79
|
+
const filesToProcess = await parseImportInputFile(inputFile);
|
|
80
|
+
if ( iiifVersion===2 ) {
|
|
81
|
+
await importAnnotationList(fastifyClient, filesToProcess);
|
|
82
|
+
} else {
|
|
83
|
+
await importAnnotationPage(fastifyClient, filesToProcess);
|
|
104
84
|
}
|
|
105
|
-
|
|
106
|
-
|
|
85
|
+
// exit
|
|
86
|
+
await fastifyClient.stop();
|
|
107
87
|
}
|
|
108
88
|
|
|
109
89
|
/////////////////////////////////////////
|
|
110
90
|
|
|
111
91
|
/** define the cli */
|
|
112
|
-
function makeImportCommand(
|
|
113
|
-
|
|
114
|
-
// argument and option name syntax:
|
|
115
|
-
// --opt-name <requiredVal> => you mst provide a value after --opt-name
|
|
116
|
-
// --opt-name [optionalVal] => if `optionalVal` is not provided, --opt-name will be treated as boolean
|
|
117
|
-
const dataTypeArg =
|
|
118
|
-
new Argument("<data-type>", "type of data to import")
|
|
119
|
-
.choices(importTypes);
|
|
92
|
+
function makeImportCommand() {
|
|
120
93
|
|
|
121
94
|
const versionOpt =
|
|
122
95
|
new Option("-i, --iiif-version <version>", "IIIF version")
|
|
@@ -125,19 +98,14 @@ function makeImportCommand(mongoClient) {
|
|
|
125
98
|
.makeOptionMandatory();
|
|
126
99
|
|
|
127
100
|
const filesOpt =
|
|
128
|
-
new Option("-f, --
|
|
101
|
+
new Option("-f, --file <file>", "file containing paths to AnnotationLists or AnnotationPages to process (1 line per path)")
|
|
129
102
|
.makeOptionMandatory();
|
|
130
103
|
|
|
131
|
-
const listFilesOpt =
|
|
132
|
-
new Option("-l, --list-files", "flag indicating that --files points to a file containing a list of JSON files to process (1 line per path)")
|
|
133
|
-
|
|
134
104
|
return new Command("import")
|
|
135
105
|
.description("import data into aiiinotate")
|
|
136
|
-
.addArgument(dataTypeArg)
|
|
137
106
|
.addOption(filesOpt)
|
|
138
107
|
.addOption(versionOpt)
|
|
139
|
-
.
|
|
140
|
-
.action((dataType, options, command) => action(mongoClient, command, dataType, options))
|
|
108
|
+
.action((options, command) => action(command, options))
|
|
141
109
|
}
|
|
142
110
|
|
|
143
111
|
export default makeImportCommand;
|
package/cli/index.js
CHANGED
|
@@ -13,7 +13,6 @@ import { Command, Option } from "commander";
|
|
|
13
13
|
|
|
14
14
|
// import dotenvx from "dotenvx";
|
|
15
15
|
|
|
16
|
-
import makeMongoClient from "#cli/utils/mongoClient.js";
|
|
17
16
|
import makeImportCommand from "#cli/import.js";
|
|
18
17
|
import makeMigrateCommand from "#cli/migrate.js";
|
|
19
18
|
import makeServeCommand from "#cli/serve.js";
|
|
@@ -29,25 +28,11 @@ function makeCli() {
|
|
|
29
28
|
Run individual commands to see command-specific help.
|
|
30
29
|
`.replace(/^\s+/gm, "");
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
new Option("--env <env-file>", "path to .env file").makeOptionMandatory();
|
|
34
|
-
|
|
35
|
-
// NOTE: how do we load the env variables ? it's a bit unorthodox:
|
|
36
|
-
// - the CLI requires to use a global `--env` option with a path to the .env file.
|
|
37
|
-
// - we use the hook `preAction` that is called before any (sub-)command's `action` function is called.
|
|
38
|
-
// - in the `preAction` hook, we call `loadEnv` that will load all env files defined in the `.env` file.
|
|
39
|
-
// - this way, all env variables will be defined in the subcommand's `action` methods (and children).
|
|
40
|
-
// this is based on https://github.com/tj/commander.js/issues/563#issuecomment-520112985 but replaxes `.on` with `.action`, which works better.
|
|
41
|
-
// WARNING: this means that the env variables can't be used in (sub-)commands BEFORE `action()` has been called.
|
|
31
|
+
// NOTE: before running, it is necessary to load env variables.
|
|
42
32
|
const cli = new Command();
|
|
43
33
|
cli
|
|
44
34
|
.name("aiiinotate")
|
|
45
35
|
.description(desc)
|
|
46
|
-
.usage("--env <path-to-your-env-file> -- <command> [options]")
|
|
47
|
-
.addOption(envFileOpt)
|
|
48
|
-
.hook("preAction", (thisCommand, actionCommand) => {
|
|
49
|
-
loadEnv(thisCommand.opts().env);
|
|
50
|
-
})
|
|
51
36
|
.addCommand(makeServeCommand())
|
|
52
37
|
.addCommand(makeImportCommand())
|
|
53
38
|
.addCommand(makeMigrateCommand());
|
|
@@ -56,4 +41,4 @@ function makeCli() {
|
|
|
56
41
|
return cli;
|
|
57
42
|
}
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
makeCli();
|
package/cli/migrate.js
CHANGED
|
@@ -112,7 +112,8 @@ function makeMigrateCommand() {
|
|
|
112
112
|
new Argument("<migration-op>", "name of migration operation").choices(allowedMigrateOp);
|
|
113
113
|
|
|
114
114
|
const migrationNameOpt =
|
|
115
|
-
new Option("-n, --migration-name <name>", "name of migration (for 'make' argument)")
|
|
115
|
+
new Option("-n, --migration-name <name>", "name of migration (for 'make' argument)")
|
|
116
|
+
.makeOptionMandatory();
|
|
116
117
|
|
|
117
118
|
return new Command("migrate")
|
|
118
119
|
.description("run database migrations")
|
package/cli/serve.js
CHANGED
|
@@ -2,13 +2,13 @@ import serve from "#src/server.js";
|
|
|
2
2
|
|
|
3
3
|
import { Command, Option, Argument } from "commander";
|
|
4
4
|
|
|
5
|
-
/** @typedef {import("#types").
|
|
5
|
+
/** @typedef {import("#types").ServeModeType} ServeModeType */
|
|
6
6
|
|
|
7
|
-
const serveModeValues = ["
|
|
7
|
+
const serveModeValues = ["dev", "prod"];
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @param {import('commander').Command} command
|
|
11
|
-
* @param {
|
|
11
|
+
* @param {ServeModeType} serveMode
|
|
12
12
|
*/
|
|
13
13
|
async function action(command, serveMode) {
|
|
14
14
|
await serve(serveMode);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import build from "#src/app.js";
|
|
2
|
+
|
|
3
|
+
/** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
|
|
4
|
+
/** @typedef {import("#types").FastifyReplyType} FastifyReplyType */
|
|
5
|
+
/** @typedef {import("#types").InsertResponseType} InsertResponseType */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* a client to interact with the fastify app.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import FastifyClient from "#cli/utils/fastifyClient.js";
|
|
12
|
+
* const fastify = new FastifyClient();
|
|
13
|
+
* // load the fastify instance asynchronously
|
|
14
|
+
* await fastify.build();
|
|
15
|
+
* // run operations
|
|
16
|
+
* await fastify.importAnnotationList(...);
|
|
17
|
+
*/
|
|
18
|
+
class FastifyClient {
|
|
19
|
+
constructor () { }
|
|
20
|
+
|
|
21
|
+
// NOTE: instanciating this.fastify is asynchronous and a constructor must be synchronous,
|
|
22
|
+
// so we use a builder to set this.fastify, following this pattern: https://stackoverflow.com/a/43433773
|
|
23
|
+
async build() {
|
|
24
|
+
/** @type {FastifyInstanceType} */
|
|
25
|
+
this.fastify = await build("default");
|
|
26
|
+
// TODO find way to actually completely disable logging because it makes the CLI UI uglyyy !
|
|
27
|
+
// log only fastify errors
|
|
28
|
+
this.fastify.log.level = "error";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async stop() {
|
|
32
|
+
await this.fastify.close();
|
|
33
|
+
this.fastify.log.info("fastify instance successfully closed.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} route
|
|
38
|
+
* @returns {Promise<FastifyReplyType>}
|
|
39
|
+
*/
|
|
40
|
+
injectGet(route) {
|
|
41
|
+
return this.fastify.inject({
|
|
42
|
+
method: "GET",
|
|
43
|
+
url: route,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} route
|
|
49
|
+
* @param {object} payload
|
|
50
|
+
* @returns {Promise<FastifyReplyType>}
|
|
51
|
+
*/
|
|
52
|
+
injectPost(route, payload) {
|
|
53
|
+
return this.fastify.inject({
|
|
54
|
+
method: "POST",
|
|
55
|
+
url: route,
|
|
56
|
+
payload: payload
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async importAnnotationPage(annotationPage) {
|
|
61
|
+
// TODO
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {object} annotationList - IIIF 2 annotationList
|
|
66
|
+
* @returns {[number, Promise<InsertResponseType>]} - [ statusCode, response ]
|
|
67
|
+
*/
|
|
68
|
+
async importAnnotationList(annotationList) {
|
|
69
|
+
const r = await this.injectPost("/annotations/2/createMany", annotationList);
|
|
70
|
+
return [ r.statusCode, r.json() ];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default FastifyClient;
|
package/cli/utils/io.js
CHANGED
|
@@ -54,53 +54,22 @@ function fileArrayValidate (fileArr) {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* `file` is a path to a file containing paths to other files (1 file per line).
|
|
57
|
-
*
|
|
57
|
+
* parse the file and return an array of absolute paths to files.
|
|
58
58
|
* @param {str} file
|
|
59
|
-
* @returns {string[]}
|
|
59
|
+
* @returns {Promise<string[]>}
|
|
60
60
|
*/
|
|
61
|
-
async function
|
|
61
|
+
async function parseImportInputFile(file) {
|
|
62
62
|
// read `file` split it by lines, remove empty lines
|
|
63
63
|
const fileArr =
|
|
64
64
|
fileRead(file)
|
|
65
65
|
.split("\n")
|
|
66
66
|
.filter(l => !l.match(/^\s*$/g));
|
|
67
|
-
return fileArrayValidate(fileArr);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* get the files to import and return them as absolute paths
|
|
72
|
-
*
|
|
73
|
-
* `fileArr` is a list of paths to either:
|
|
74
|
-
* - (fileArr=false) JSON files to import
|
|
75
|
-
* - (fileArr=true) text files containing paths to the JSONS to import
|
|
76
|
-
* => take `fileArr`, validate that all files exist, extract all filepaths
|
|
77
|
-
* from `fileArr`, and return the array of actual JSON paths to process.
|
|
78
|
-
*
|
|
79
|
-
* @param {string[]} fileArr
|
|
80
|
-
* @param {boolean} listFiles
|
|
81
|
-
* @returns {string[]} the list of existing files to process.
|
|
82
|
-
*/
|
|
83
|
-
function getFilesToProcess(fileArr, listFiles=false) {
|
|
84
|
-
let filesToProcess = fileArrayValidate(fileArr);
|
|
85
|
-
|
|
86
|
-
// if `listFile`, open the files containing paths of files to proces, and redo the same validation process for each file in a list file.
|
|
87
|
-
if ( listFiles ) {
|
|
88
|
-
let filesInListFiles = []
|
|
89
|
-
|
|
90
|
-
filesToProcess.map((theListFile) => {
|
|
91
|
-
const files = getFilesInListFile(theListFile);
|
|
92
|
-
filesInListFiles = filesInListFiles.concat(files);
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
filesToProcess = [...new Set(filesInListFiles)]; // deduplicate
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return filesToProcess
|
|
67
|
+
return [...new Set(fileArrayValidate(fileArr))];
|
|
99
68
|
}
|
|
100
69
|
|
|
101
70
|
export {
|
|
102
71
|
fileRead,
|
|
103
72
|
fileOk,
|
|
104
73
|
fileArrayValidate,
|
|
105
|
-
|
|
74
|
+
parseImportInputFile
|
|
106
75
|
}
|
package/cli/utils/mongoClient.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { MongoClient } from "mongodb";
|
|
2
2
|
|
|
3
|
+
import { MONGODB_CONNSTRING, MONGODB_DB } from "#constants";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* load a mongo client and connect it to the database. exists if there's an error
|
|
5
7
|
* @returns {import("mongodb").MongoClient}
|
|
6
8
|
*/
|
|
7
9
|
function loadMongoClient() {
|
|
8
10
|
try {
|
|
9
|
-
const client = new MongoClient(
|
|
10
|
-
client.db(
|
|
11
|
+
const client = new MongoClient(MONGODB_CONNSTRING);
|
|
12
|
+
client.db(MONGODB_DB);
|
|
11
13
|
return client;
|
|
12
14
|
} catch (err) {
|
|
13
15
|
console.error(`mongoClient: could not connect to DB because of error ${err.message}`);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* a progress bar
|
|
3
|
+
* @example
|
|
4
|
+
* const total = 100;
|
|
5
|
+
* const ms = 10;
|
|
6
|
+
* const pb = new ProgressBar({ desc: "Progressbar example", total: total });
|
|
7
|
+
* const sleep = (pb, i) => {
|
|
8
|
+
* pb.update(i);
|
|
9
|
+
* return new Promise((res, rej) => setTimeout(res, ms));
|
|
10
|
+
* };
|
|
11
|
+
* for (let i=0; i<=total; i++) {
|
|
12
|
+
* await sleep(pb, i);
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
class ProgressBar {
|
|
16
|
+
constructor(options = {
|
|
17
|
+
desc: "Progress",
|
|
18
|
+
total: 100
|
|
19
|
+
}) {
|
|
20
|
+
this.desc = options.desc
|
|
21
|
+
this.total = options.total
|
|
22
|
+
this.current = 0;
|
|
23
|
+
this.startTime = Date.now();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
update(current) {
|
|
27
|
+
this.current = Math.min(current, this.total);
|
|
28
|
+
this.render();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
render() {
|
|
32
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
33
|
+
|
|
34
|
+
const percent = this.total === 0 ? 100 : (this.current / this.total) * 100;
|
|
35
|
+
const percentStr = percent.toFixed(1) + "%";
|
|
36
|
+
|
|
37
|
+
// calculate elapsed and estimated time
|
|
38
|
+
const elapsedTime = Date.now() - this.startTime;
|
|
39
|
+
const elapsedSec = Math.floor(elapsedTime / 1000);
|
|
40
|
+
const rate = this.current / (elapsedTime / 1000);
|
|
41
|
+
const remainingTime = rate > 0 ? Math.floor((this.total - this.current) / rate) : 0;
|
|
42
|
+
|
|
43
|
+
const timeStr = `${this.formatTime(elapsedSec)}/${this.formatTime(remainingTime)}`;
|
|
44
|
+
const countStr = `${this.current}/${this.total}`;
|
|
45
|
+
|
|
46
|
+
// calculate available space for bar
|
|
47
|
+
const prefix = `${this.desc} [`;
|
|
48
|
+
const suffix = `] ${percentStr} ${countStr} ${timeStr}`;
|
|
49
|
+
const availableWidth = terminalWidth - prefix.length - suffix.length;
|
|
50
|
+
|
|
51
|
+
if (availableWidth < 10) {
|
|
52
|
+
process.stdout.write(`\r${this.desc}: ${percentStr}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// build progress bar
|
|
57
|
+
const filledWidth = Math.floor((this.current / this.total) * availableWidth);
|
|
58
|
+
const emptyWidth = availableWidth - filledWidth;
|
|
59
|
+
const bar = "█".repeat(filledWidth) + "░".repeat(emptyWidth);
|
|
60
|
+
|
|
61
|
+
// build and display complete line
|
|
62
|
+
const line = prefix + bar + suffix;
|
|
63
|
+
process.stdout.write("\r" + line);
|
|
64
|
+
|
|
65
|
+
// new line when complete
|
|
66
|
+
if (this.current === this.total) {
|
|
67
|
+
process.stdout.write("\n");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
formatTime(seconds) {
|
|
72
|
+
const h = Math.floor(seconds / 3600);
|
|
73
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
74
|
+
const s = seconds % 60;
|
|
75
|
+
|
|
76
|
+
if (h > 0) {
|
|
77
|
+
return `${h}h${m}m`;
|
|
78
|
+
} else if (m > 0) {
|
|
79
|
+
return `${m}m${s}s`;
|
|
80
|
+
} else {
|
|
81
|
+
return `${s}s`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default ProgressBar
|
package/config/.env.template
CHANGED
|
@@ -14,8 +14,15 @@ AIIINOTATE_HOST=127.0.0.1
|
|
|
14
14
|
# HTTP scheme: HTTP or HTTPS. should be HTTP in dev and in docker
|
|
15
15
|
AIIINOTATE_SCHEME=http
|
|
16
16
|
|
|
17
|
+
# where to output logs. either: "stdout"|"file"|"stdout+file"|"off"
|
|
18
|
+
AIIINOTATE_LOG_TARGET=stdout
|
|
19
|
+
# directory to save logs to (if AIIINOTATE_LOG_TARGET enables logging to file)
|
|
20
|
+
AIIINOTATE_LOG_DIR=
|
|
21
|
+
|
|
17
22
|
# max number of items to display per result page
|
|
18
23
|
AIIINOTATE_PAGE_SIZE=5000
|
|
24
|
+
# "true"|"false". strict error throwing when inserting annotations. equivalent to `throwOnCanvasIndexError=true` (raise an error if an annotation's target manifest can't be found) and `throwOnXywhError=true` (raise an error if you can't extract a bounding box for an annotation's target). use in controlled environments where you know precisely the structure of your annotations and where you know that your manifests are accessible through HTTP.
|
|
25
|
+
AIIINOTATE_STRICT_MODE=false
|
|
19
26
|
|
|
20
27
|
# IGNORE
|
|
21
28
|
AIIINOTATE_BASE_URL="$AIIINOTATE_SCHEME://$AIIINOTATE_HOST:$AIIINOTATE_PORT"
|