jskos-server 2.4.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/.dockerignore +20 -0
- package/.editorconfig +9 -0
- package/.github/workflows/docker.yml +59 -0
- package/.github/workflows/gh-pages.yml +23 -0
- package/.github/workflows/gh-release.yml +19 -0
- package/.github/workflows/test.yml +39 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +2710 -0
- package/bin/extra.js +81 -0
- package/bin/import.js +438 -0
- package/bin/mongodb.js +21 -0
- package/bin/reset.js +257 -0
- package/bin/upgrade.js +34 -0
- package/config/config.default.json +88 -0
- package/config/config.schema.json +877 -0
- package/config/config.test.json +107 -0
- package/config/index.js +77 -0
- package/config/setup.js +212 -0
- package/depdendencies.png +0 -0
- package/docker/.env +1 -0
- package/docker/Dockerfile +20 -0
- package/docker/README.md +175 -0
- package/docker/docker-compose.yml +29 -0
- package/docker/docker-entrypoint.sh +8 -0
- package/docker/mongo-initdb.d/mongo_setup.sh +22 -0
- package/ecosystem.example.json +7 -0
- package/errors/index.js +94 -0
- package/eslint.config.js +17 -0
- package/index.js +10 -0
- package/models/annotations.js +13 -0
- package/models/concepts.js +12 -0
- package/models/concordances.js +12 -0
- package/models/index.js +33 -0
- package/models/mappings.js +20 -0
- package/models/meta.js +21 -0
- package/models/registries.js +12 -0
- package/models/schemes.js +12 -0
- package/package.json +91 -0
- package/routes/annotations.js +83 -0
- package/routes/common.js +86 -0
- package/routes/concepts.js +64 -0
- package/routes/concordances.js +86 -0
- package/routes/data.js +19 -0
- package/routes/mappings.js +108 -0
- package/routes/registries.js +24 -0
- package/routes/schemes.js +72 -0
- package/routes/validate.js +37 -0
- package/server.js +190 -0
- package/services/abstract.js +328 -0
- package/services/annotations.js +237 -0
- package/services/concepts.js +459 -0
- package/services/concordances.js +264 -0
- package/services/data.js +30 -0
- package/services/index.js +34 -0
- package/services/mappings.js +978 -0
- package/services/registries.js +319 -0
- package/services/schemes.js +318 -0
- package/services/validate.js +39 -0
- package/status.schema.json +145 -0
- package/test/abstract-service.js +36 -0
- package/test/annotations/annotation.json +13 -0
- package/test/api.js +2481 -0
- package/test/chai.js +14 -0
- package/test/changes.js +179 -0
- package/test/concepts/conceptNoFileEnding +4 -0
- package/test/concepts/concepts-ddc-6-60-61-62.json +123 -0
- package/test/concordances/concordances.ndjson +2 -0
- package/test/config.js +26 -0
- package/test/configs/complex-config.json +90 -0
- package/test/configs/empty-object.json +1 -0
- package/test/configs/fail-array.json +1 -0
- package/test/configs/fail-empty.json +0 -0
- package/test/configs/fail-mapping-only-props1.json +5 -0
- package/test/configs/fail-mapping-only-props2.json +5 -0
- package/test/configs/fail-mapping-only-props3.json +5 -0
- package/test/configs/fail-mapping-only-props4.json +5 -0
- package/test/configs/fail-nonexisting-prop.json +3 -0
- package/test/configs/fail-port-string.json +3 -0
- package/test/configs/fail-registry-types.json +16 -0
- package/test/configs/registry-types.json +16 -0
- package/test/data-write.js +784 -0
- package/test/eslint.js +22 -0
- package/test/import-reset.js +287 -0
- package/test/infer-mappings.js +340 -0
- package/test/ipcheck.js +287 -0
- package/test/mappings/README.md +1 -0
- package/test/mappings/ddc-gnd-1.mapping.json +33 -0
- package/test/mappings/ddc-gnd-2.mapping.json +67 -0
- package/test/mappings/mapping-ddc-gnd-noScheme.json +145 -0
- package/test/mappings/mapping-ddc-gnd.json +175 -0
- package/test/mappings/mappings-ddc.json +214 -0
- package/test/registries/registries.ndjson +2 -0
- package/test/services.js +557 -0
- package/test/terminologies/terminologies.json +94 -0
- package/test/test-utils.js +182 -0
- package/test/utils.js +425 -0
- package/test/validate.js +226 -0
- package/utils/adjust.js +206 -0
- package/utils/auth.js +154 -0
- package/utils/changes.js +88 -0
- package/utils/db.js +106 -0
- package/utils/ipcheck.js +76 -0
- package/utils/middleware.js +636 -0
- package/utils/searchHelper.js +153 -0
- package/utils/status.js +77 -0
- package/utils/users.js +7 -0
- package/utils/utils.js +114 -0
- package/utils/uuid.js +6 -0
- package/utils/version.js +324 -0
- package/views/base.ejs +172 -0
package/bin/extra.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import yesno from "yesno"
|
|
4
|
+
import * as db from "../utils/db.js"
|
|
5
|
+
import { schemeService } from "../services/schemes.js"
|
|
6
|
+
import jskos from "jskos-tools"
|
|
7
|
+
import { Mapping } from "../models/mappings.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Map of async scripts.
|
|
11
|
+
*
|
|
12
|
+
* Note that a connection to the database will be established before the script is called.
|
|
13
|
+
*/
|
|
14
|
+
const scripts = {
|
|
15
|
+
async supplementNotationsInMappings() {
|
|
16
|
+
const mappings = await Mapping.find({
|
|
17
|
+
$or: [
|
|
18
|
+
{ "from.memberSet.notation": { $exists: false }, "from.memberSet.uri": { $exists: true } },
|
|
19
|
+
{ "to.memberSet.notation": { $exists: false }, "to.memberSet.uri": { $exists: true } },
|
|
20
|
+
],
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
console.log(`Found ${mappings.length} mappings with missing notations...`)
|
|
24
|
+
|
|
25
|
+
if (mappings.length && !(await yesno({
|
|
26
|
+
question: `Update ${mappings.length} mappings with notations (if possible)?`,
|
|
27
|
+
defaultValue: false,
|
|
28
|
+
}))) {
|
|
29
|
+
console.log("Aborting...")
|
|
30
|
+
await db.disconnect()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let changedMappings = 0
|
|
35
|
+
for (const mapping of mappings) {
|
|
36
|
+
const changedPaths = []
|
|
37
|
+
for (const side of ["from", "to"]) {
|
|
38
|
+
let scheme = mapping[`${side}Scheme`]
|
|
39
|
+
let concepts = mapping[side].memberSet
|
|
40
|
+
scheme = await schemeService.getScheme(scheme.uri)
|
|
41
|
+
scheme = scheme && new jskos.ConceptScheme(scheme)
|
|
42
|
+
if (!scheme) {
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
concepts.forEach((concept, index) => {
|
|
46
|
+
if (concept.notation && concept.notation.length) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
const notation = scheme.notationFromUri(concept.uri)
|
|
50
|
+
if (!notation) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
concept.notation = [notation]
|
|
54
|
+
changedPaths.push(`${side}.memberSet.${index}.notation`)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
if (changedPaths.length) {
|
|
58
|
+
changedPaths.forEach(path => mapping.markModified(path))
|
|
59
|
+
await mapping.save()
|
|
60
|
+
changedMappings += 1
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`- Supplemented ${changedMappings} mappings with notations.`)
|
|
65
|
+
console.log(`- ${mappings.length - changedMappings} mappings could not be adjusted.`)
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const scriptName = process.argv[2]
|
|
70
|
+
|
|
71
|
+
if (!scripts[scriptName]) {
|
|
72
|
+
console.error(`No supplemental script with name ${scriptName} could be found. The following scripts are available:`)
|
|
73
|
+
Object.keys(scripts).forEach(name => console.log(`- ${name}`))
|
|
74
|
+
process.exit(1)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
(async function() {
|
|
78
|
+
await db.connect(false)
|
|
79
|
+
await scripts[scriptName]()
|
|
80
|
+
await db.disconnect()
|
|
81
|
+
})()
|
package/bin/import.js
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import meow from "meow"
|
|
4
|
+
const cli = meow(`
|
|
5
|
+
Usage
|
|
6
|
+
$ npm run import -- [options] [type input]
|
|
7
|
+
|
|
8
|
+
type and input are required unless with options --indexes.
|
|
9
|
+
input can be a .json file (single object or array), an .ndjson file (newline delimited JSON),
|
|
10
|
+
or a URL referring to a JSON or NDJSON source.
|
|
11
|
+
|
|
12
|
+
Note that with a URL, it either has to have a proper file ending or provide the correct content type
|
|
13
|
+
(i.e. application/json or application/x-ndjson). If not, the --format option has to be provided.
|
|
14
|
+
|
|
15
|
+
Options
|
|
16
|
+
GNU long option Option Meaning
|
|
17
|
+
--indexes -i Create indexes without import (can also be used in addition to import)
|
|
18
|
+
--quiet -q Only output warnings and errors
|
|
19
|
+
--format -f Either json or ndjson. Defaults to file ending or content type if available.
|
|
20
|
+
--scheme -s Only for concepts. Adds imported concepts to a scheme for specified URI.
|
|
21
|
+
The scheme must already exist. (topConceptOf still needs to be set if applicable.)
|
|
22
|
+
--concordance -c Only for mappings. Adds imported mappings into a concordance for specified URI.
|
|
23
|
+
The concordance must already exist.
|
|
24
|
+
--noreplace -n EXPERIMENTAL. When given, bulk writing will use insertOne instead of replaceOne,
|
|
25
|
+
meaning that existing entities will not be overridden.
|
|
26
|
+
Note that an error will be thrown when even one of the entities already exist.
|
|
27
|
+
--nobulk -b Disable bulk import no not ignore and filter out invalid entities
|
|
28
|
+
--set-api EXPERIMENTAL. Onlt for concepts. Will update the scheme's \`API\` property after
|
|
29
|
+
importing concepts.
|
|
30
|
+
|
|
31
|
+
Examples
|
|
32
|
+
$ npm run import -- --indexes
|
|
33
|
+
$ npm run import -- schemes schemes.ndjson
|
|
34
|
+
$ npm run import -- concepts concepts.ndjson
|
|
35
|
+
$ npm run import -- registry registries.ndjson
|
|
36
|
+
`, {
|
|
37
|
+
importMeta: import.meta,
|
|
38
|
+
flags: {
|
|
39
|
+
reset: {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
shortFlag: "r",
|
|
42
|
+
default: false,
|
|
43
|
+
},
|
|
44
|
+
indexes: {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
shortFlag: "i",
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
quiet: {
|
|
50
|
+
type: "boolean",
|
|
51
|
+
shortFlag: "q",
|
|
52
|
+
default: false,
|
|
53
|
+
},
|
|
54
|
+
format: {
|
|
55
|
+
type: "string",
|
|
56
|
+
shortFlag: "f",
|
|
57
|
+
default: "",
|
|
58
|
+
},
|
|
59
|
+
scheme: {
|
|
60
|
+
type: "string",
|
|
61
|
+
shortFlag: "s",
|
|
62
|
+
default: "",
|
|
63
|
+
},
|
|
64
|
+
setApi: {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
default: false,
|
|
67
|
+
},
|
|
68
|
+
concordance: {
|
|
69
|
+
type: "string",
|
|
70
|
+
shortFlag: "c",
|
|
71
|
+
default: "",
|
|
72
|
+
},
|
|
73
|
+
noreplace: {
|
|
74
|
+
type: "boolean",
|
|
75
|
+
shortFlag: "n",
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
78
|
+
nobulk: {
|
|
79
|
+
type: "boolean",
|
|
80
|
+
shortFlag: "b",
|
|
81
|
+
default: false,
|
|
82
|
+
},
|
|
83
|
+
help: {
|
|
84
|
+
type: "boolean",
|
|
85
|
+
shortFlag: "h",
|
|
86
|
+
default: false,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const indexes = cli.flags.indexes
|
|
92
|
+
|
|
93
|
+
if (cli.flags.help || (!cli.input.length && !indexes)) {
|
|
94
|
+
cli.showHelp()
|
|
95
|
+
process.exit(0)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const log = (...args) => {
|
|
99
|
+
if (!cli.flags.quiet) {
|
|
100
|
+
console.log(...args)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const logError = ({ message, showHelp = false, exit = false }) => {
|
|
104
|
+
console.error(` Error: ${message}`)
|
|
105
|
+
if (showHelp) {
|
|
106
|
+
cli.showHelp()
|
|
107
|
+
}
|
|
108
|
+
if (exit) {
|
|
109
|
+
process.exit(1)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --reset was removed
|
|
114
|
+
if (cli.flags.reset) {
|
|
115
|
+
logError({
|
|
116
|
+
message: "The option --reset was removed from the import script in version 1.2.0.",
|
|
117
|
+
showHelp: true,
|
|
118
|
+
exit: true,
|
|
119
|
+
})
|
|
120
|
+
// TODO: Mention URL to documentation and/or script that replaces --reset.
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
import jskos from "jskos-tools"
|
|
124
|
+
import fs from "node:fs"
|
|
125
|
+
const input = cli.input[1] || ""
|
|
126
|
+
|
|
127
|
+
// Parse type
|
|
128
|
+
const type = jskos.guessObjectType(cli.input[0], true)
|
|
129
|
+
if (cli.input[0] && !type) {
|
|
130
|
+
logError({
|
|
131
|
+
message: `Invalid <type> argument: ${cli.input[0] || ""}`,
|
|
132
|
+
showHelp: true,
|
|
133
|
+
exit: true,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
if (!indexes && !type) {
|
|
137
|
+
logError({
|
|
138
|
+
message: "The <type> argument is necessary to import data.",
|
|
139
|
+
showHelp: true,
|
|
140
|
+
exit: true,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
if (cli.flags.scheme && type != "concept") {
|
|
144
|
+
logError({
|
|
145
|
+
message: `The -s option is not compatible with type ${type}.`,
|
|
146
|
+
showHelp: true,
|
|
147
|
+
exit: true,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
if (cli.flags.setApi && type != "concept") {
|
|
151
|
+
logError({
|
|
152
|
+
message: `The --set-api option is not compatible with type ${type}.`,
|
|
153
|
+
showHelp: true,
|
|
154
|
+
exit: true,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
if (cli.flags.concordance && type != "mapping") {
|
|
158
|
+
logError({
|
|
159
|
+
message: `The -c option is not compatible with type ${type}.`,
|
|
160
|
+
showHelp: true,
|
|
161
|
+
exit: true,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (cli.flags.bulk && ["concordance","concordance","registry"].find(type)) {
|
|
166
|
+
logError({
|
|
167
|
+
message: `The --nobulk option is not supported with type ${type}`,
|
|
168
|
+
exit: true,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check input parameter if necessary
|
|
173
|
+
const inputIsUrl = !!input.match(/^https?:\/\//)
|
|
174
|
+
if (!indexes) {
|
|
175
|
+
if (!input || (!inputIsUrl && !fs.existsSync(input))) {
|
|
176
|
+
logError({
|
|
177
|
+
message: `Invalid or missing <file>: ${input || ""}`,
|
|
178
|
+
showHelp: true,
|
|
179
|
+
exit: true,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check format parameter
|
|
185
|
+
let format
|
|
186
|
+
if (["json", "ndjson"].includes(cli.flags.format)) {
|
|
187
|
+
format = cli.flags.format
|
|
188
|
+
} else if (cli.flags.format) {
|
|
189
|
+
logError({
|
|
190
|
+
message: `Unknown format ${cli.flags.format}, please provide one of json or ndjson, or leave empty.`,
|
|
191
|
+
showHelp: true,
|
|
192
|
+
exit: true,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
log(`Start of import script: ${new Date()}`)
|
|
197
|
+
|
|
198
|
+
// Log all parameters
|
|
199
|
+
input && log(`- will import ${type} from ${inputIsUrl ? "URL" : "local file"} ${input}`)
|
|
200
|
+
indexes && log("- will create indexes", type ? `for type ${type}` : "for all types")
|
|
201
|
+
format && log(`- with format: ${format}`)
|
|
202
|
+
log("")
|
|
203
|
+
|
|
204
|
+
import { validate } from "jskos-validate"
|
|
205
|
+
import config from "../config/index.js"
|
|
206
|
+
import { v5 as uuidv5 } from "uuid"
|
|
207
|
+
import path from "node:path"
|
|
208
|
+
import * as anystream from "json-anystream"
|
|
209
|
+
import * as db from "../utils/db.js"
|
|
210
|
+
|
|
211
|
+
import { createServices } from "../services/index.js"
|
|
212
|
+
const services = createServices(config)
|
|
213
|
+
const allTypes = Object.keys(services)
|
|
214
|
+
|
|
215
|
+
// Also import models for Mapping and Concordance
|
|
216
|
+
// TODO: This won't be needed if these are imported through the service as well.
|
|
217
|
+
import { Mapping, Concordance } from "../models/index.js"
|
|
218
|
+
import { bulkOperationForEntities, addMappingSchemes} from "../utils/utils.js"
|
|
219
|
+
|
|
220
|
+
;(async () => {
|
|
221
|
+
try {
|
|
222
|
+
await db.connect()
|
|
223
|
+
} catch (error) {
|
|
224
|
+
logError({
|
|
225
|
+
message: error,
|
|
226
|
+
exit: true,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
log("Connection to database established.")
|
|
230
|
+
log()
|
|
231
|
+
|
|
232
|
+
if (indexes) {
|
|
233
|
+
log("Creating indexes...")
|
|
234
|
+
let types = type ? [type] : allTypes
|
|
235
|
+
for (let type of types) {
|
|
236
|
+
await services[type].createIndexes()
|
|
237
|
+
log(`... done (${type})`)
|
|
238
|
+
}
|
|
239
|
+
log()
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (input) {
|
|
243
|
+
let concordance
|
|
244
|
+
if (cli.flags.concordance) {
|
|
245
|
+
// Query concordance from database
|
|
246
|
+
concordance = await services.concordance.retrieveItem(cli.flags.concordance)
|
|
247
|
+
if (!concordance) {
|
|
248
|
+
logError({
|
|
249
|
+
message: `Concordance with URI ${cli.flags.concordance} not found, aborting...`,
|
|
250
|
+
exit: true,
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
await doImport({ input, format, type, concordance })
|
|
256
|
+
} catch (error) {
|
|
257
|
+
logError({ message: `Import failed - ${error}` })
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
db.disconnect()
|
|
262
|
+
log()
|
|
263
|
+
log(`End of import script: ${new Date()}`)
|
|
264
|
+
})()
|
|
265
|
+
|
|
266
|
+
async function doImport({ input, format, type, concordance }) {
|
|
267
|
+
const bodyStream = await anystream.make(input, format)
|
|
268
|
+
const bulk = !cli.flags.nobulk
|
|
269
|
+
const bulkReplace = !cli.flags.noreplace
|
|
270
|
+
|
|
271
|
+
if (type == "scheme") {
|
|
272
|
+
log("Importing schemes...")
|
|
273
|
+
const result = await services.scheme.createItem({ bodyStream, bulk, bulkReplace })
|
|
274
|
+
log(`... done: ${Array.isArray(result) ? result.length : 1} schemes imported.`)
|
|
275
|
+
} else if (type == "concept") {
|
|
276
|
+
log("Importing concepts...")
|
|
277
|
+
// TODO: Find way to output progress.
|
|
278
|
+
const result = await services.concept.createItem({
|
|
279
|
+
bodyStream, bulk, bulkReplace,
|
|
280
|
+
scheme: cli.flags.scheme,
|
|
281
|
+
setApi: cli.flags.setApi,
|
|
282
|
+
})
|
|
283
|
+
log(`... done: ${Array.isArray(result) ? result.length : 1} concepts imported.`)
|
|
284
|
+
} else if (type == "mapping") {
|
|
285
|
+
// TODO: Eventually, this should also be done through the service.
|
|
286
|
+
let mappings = []
|
|
287
|
+
// Keep track of concordances that need to be adjusted
|
|
288
|
+
const concordanceUrisToAdjust = new Set()
|
|
289
|
+
if (concordance?.uri) {
|
|
290
|
+
concordanceUrisToAdjust.add(concordance.uri)
|
|
291
|
+
}
|
|
292
|
+
let imported = 0
|
|
293
|
+
let total = 0
|
|
294
|
+
const saveMappings = async (mappings) => {
|
|
295
|
+
const result = await Mapping.bulkWrite(bulkOperationForEntities({ entities: mappings, replace: !cli.flags.noreplace }))
|
|
296
|
+
imported += result.insertedCount + result.upsertedCount + result.modifiedCount
|
|
297
|
+
console.log(`... ${imported} done ...`)
|
|
298
|
+
}
|
|
299
|
+
// Name the loop
|
|
300
|
+
mappingLoop: for await (let object of bodyStream) {
|
|
301
|
+
total += 1
|
|
302
|
+
if (!validate[type] || !validate[type](object)) {
|
|
303
|
+
logError({ message: `Could not validate ${type} number ${total}: ${object && object.uri}` })
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
// Adjustments for mapping
|
|
307
|
+
if (object.uri) {
|
|
308
|
+
if (object.uri.startsWith(config.baseUrl)) {
|
|
309
|
+
object._id = object.uri.slice(object.uri.lastIndexOf("/") + 1)
|
|
310
|
+
} else {
|
|
311
|
+
// Put URI into identifier because it's not valid for this server
|
|
312
|
+
object.identifier = [].concat(object.identifier || [], object.uri)
|
|
313
|
+
delete object.uri
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Add reference to concordance.
|
|
317
|
+
if (concordance?.uri) {
|
|
318
|
+
object.partOf = [{
|
|
319
|
+
uri: concordance.uri,
|
|
320
|
+
}]
|
|
321
|
+
} else if (object.partOf?.[0]?.uri) {
|
|
322
|
+
concordanceUrisToAdjust.add(object.partOf?.[0]?.uri)
|
|
323
|
+
}
|
|
324
|
+
// Copy creator from concordance if it doesn't exist.
|
|
325
|
+
if (!object.creator && concordance && concordance.creator) {
|
|
326
|
+
object.creator = concordance.creator
|
|
327
|
+
}
|
|
328
|
+
// Set created of concordance if created is not set
|
|
329
|
+
if (!object.created && concordance && concordance.created) {
|
|
330
|
+
object.created = concordance.created
|
|
331
|
+
}
|
|
332
|
+
// Set modified if necessary
|
|
333
|
+
if (!object.modified && concordance && concordance.modified) {
|
|
334
|
+
object.modified = concordance.modified
|
|
335
|
+
}
|
|
336
|
+
if (!object.modified && object.created) {
|
|
337
|
+
object.modified = object.created
|
|
338
|
+
}
|
|
339
|
+
// Set fromScheme and toScheme from concordance
|
|
340
|
+
addMappingSchemes(object, { concordance })
|
|
341
|
+
// Check if schemes are available and replace them with URI/notation only
|
|
342
|
+
await services.scheme.replaceSchemeProperties(object, ["fromScheme", "toScheme"])
|
|
343
|
+
// Reject mapping if either fromScheme or toScheme is missing
|
|
344
|
+
for (let field of ["fromScheme", "toScheme"]) {
|
|
345
|
+
if (!object[field]) {
|
|
346
|
+
logError({ message: `Field \`${field}\` missing for ${type} number ${total}: ${object && object.uri}` })
|
|
347
|
+
continue mappingLoop
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Add mapping identifier
|
|
351
|
+
try {
|
|
352
|
+
object.identifier = jskos.addMappingIdentifiers(object).identifier
|
|
353
|
+
} catch (error) {
|
|
354
|
+
log("Could not add identifier to mapping.", error)
|
|
355
|
+
}
|
|
356
|
+
// Generate an identifier and a URI if necessary
|
|
357
|
+
if (!object.uri) {
|
|
358
|
+
const contentIdentifier = object.identifier.find(id => id && id.startsWith("urn:jskos:mapping:content:"))
|
|
359
|
+
const concordance = object.partOf?.[0]?.uri || ""
|
|
360
|
+
if (contentIdentifier) {
|
|
361
|
+
object._id = uuidv5(contentIdentifier + concordance, config.namespace)
|
|
362
|
+
object.uri = config.baseUrl + "mappings/" + object._id
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Write to mappings
|
|
366
|
+
mappings.push(object)
|
|
367
|
+
if (mappings.length % 5000 == 0) {
|
|
368
|
+
mappings.length && await saveMappings(mappings)
|
|
369
|
+
mappings = []
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
mappings.length && await saveMappings(mappings)
|
|
373
|
+
log(`... done: ${imported} mappings imported (${total - imported} skipped).`)
|
|
374
|
+
if (concordanceUrisToAdjust.size) {
|
|
375
|
+
log(`... adjusting extent for ${concordanceUrisToAdjust.size} concordances...`)
|
|
376
|
+
await Promise.all([...concordanceUrisToAdjust].map(uri => services.concordance.postAdjustmentForConcordance(uri)))
|
|
377
|
+
log("... done.")
|
|
378
|
+
}
|
|
379
|
+
} else if (type == "concordance") {
|
|
380
|
+
// TODO: Eventually, this should also be done through the service.
|
|
381
|
+
let imported = 0
|
|
382
|
+
let total = 0
|
|
383
|
+
for await (let concordance of bodyStream) {
|
|
384
|
+
total += 1
|
|
385
|
+
// Rewrite "distribution" to "distributions" if necessary
|
|
386
|
+
if (concordance.distribution) {
|
|
387
|
+
concordance.distributions = concordance.distribution
|
|
388
|
+
delete concordance.distribution
|
|
389
|
+
}
|
|
390
|
+
// Remove distributions with same baseUrl since they will be added dynamically
|
|
391
|
+
if (concordance.distributions) {
|
|
392
|
+
concordance.distributions = concordance.distributions.filter(dist => !dist.download || !dist.download.startsWith(config.baseUrl))
|
|
393
|
+
if (!concordance.distributions.length) {
|
|
394
|
+
delete concordance.distributions
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Validation
|
|
398
|
+
if (!validate[type] || !validate[type](concordance)) {
|
|
399
|
+
logError({ message: `Could not validate ${type} number ${total}: ${concordance && concordance.uri}` })
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
const uri = concordance.uri
|
|
403
|
+
concordance._id = uri
|
|
404
|
+
const result = await Concordance.bulkWrite(bulkOperationForEntities({ entities: [concordance], replace: !cli.flags.noreplace }))
|
|
405
|
+
if (result.insertedCount + result.modifiedCount + result.upsertedCount === 1) {
|
|
406
|
+
imported += 1
|
|
407
|
+
log(`... imported concordance ${uri}, now importing its mappings...`)
|
|
408
|
+
// TODO: Should concordance be dropped?
|
|
409
|
+
let distribution = (concordance.distributions || []).find(element => element.mimetype.includes("json"))
|
|
410
|
+
if (distribution) {
|
|
411
|
+
// Build file URL
|
|
412
|
+
let url = ""
|
|
413
|
+
if (distribution.download.startsWith("http")) {
|
|
414
|
+
url = distribution.download
|
|
415
|
+
} else {
|
|
416
|
+
// ?
|
|
417
|
+
url = path.dirname(input) + "/" + distribution.download
|
|
418
|
+
}
|
|
419
|
+
await doImport({ input: url, type: "mapping", concordance })
|
|
420
|
+
} else {
|
|
421
|
+
log("... no mapping distribution found.")
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
log(`... done: ${imported} concordances imported (${total - imported} skipped).`)
|
|
426
|
+
} else if (type == "annotation") {
|
|
427
|
+
log("Importing annotations...")
|
|
428
|
+
// TODO: Find way to output progress.
|
|
429
|
+
const result = await services.annotation.createItem({ bodyStream, bulk, bulkReplace, admin: true })
|
|
430
|
+
log(`... done: ${Array.isArray(result) ? result.length : 1} annotations imported.`)
|
|
431
|
+
} else if (type == "registry") {
|
|
432
|
+
log("Importing registries...")
|
|
433
|
+
const result = await services.registry.createItem({ bodyStream, bulk, bulkReplace })
|
|
434
|
+
const imported = result?.importedCount ?? 0
|
|
435
|
+
const skipped = result?.skippedCount ?? 0
|
|
436
|
+
log(`... done: ${imported} registries imported${skipped > 0 ? ` (${skipped} skipped).` : "."}`)
|
|
437
|
+
}
|
|
438
|
+
}
|
package/bin/mongodb.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import config from "../config/index.js"
|
|
4
|
+
|
|
5
|
+
if (process.argv.includes("--debug")) {
|
|
6
|
+
process.env.MONGOMS_DEBUG=1
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// dynamic import, to take into account env
|
|
10
|
+
const { MongoMemoryServer, MongoMemoryReplSet } = await import("mongodb-memory-server")
|
|
11
|
+
|
|
12
|
+
const { port, db } = config.mongo
|
|
13
|
+
if (config.changes) {
|
|
14
|
+
const server = await MongoMemoryReplSet.create({ instanceOpts: [{ port }] })
|
|
15
|
+
console.log(`Started MongoDB replica set ${server.getUri()} database ${db}`)
|
|
16
|
+
|
|
17
|
+
} else {
|
|
18
|
+
const instance = { port, dbName: db }
|
|
19
|
+
const server = await MongoMemoryServer.create({ instance, replSet: { dbName: db } })
|
|
20
|
+
console.log(`Started MongoDB ${server.getUri()} database ${db}`)
|
|
21
|
+
}
|