From fe6b794493609c7a7370d2d3b5108b8691de546c Mon Sep 17 00:00:00 2001 From: chin Date: Mon, 31 Jul 2023 12:57:27 +0800 Subject: [PATCH 001/365] chore: first sql initial message --- app.js | 3 +- config-class.js | 164 +++--- package-lock.json | 523 ++++++++++++++++++- package.json | 4 + plugins/sql/init.js | 23 + plugins/sql/instance.js | 9 + plugins/sql/models/dicom-json-model.js | 41 ++ plugins/sql/models/patient.model.js | 51 ++ plugins/sql/models/personName.model.js | 31 ++ plugins/sql/vrTypeMapping.js | 38 ++ plugins/store-sql/index.js | 55 ++ plugins/store-sql/service/stow-rs.service.js | 62 +++ server.js | 45 +- 13 files changed, 932 insertions(+), 117 deletions(-) create mode 100644 plugins/sql/init.js create mode 100644 plugins/sql/instance.js create mode 100644 plugins/sql/models/dicom-json-model.js create mode 100644 plugins/sql/models/patient.model.js create mode 100644 plugins/sql/models/personName.model.js create mode 100644 plugins/sql/vrTypeMapping.js create mode 100644 plugins/store-sql/index.js create mode 100644 plugins/store-sql/service/stow-rs.service.js diff --git a/app.js b/app.js index 6f195df9..5833d74c 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,5 @@ -const mongodb = require("./models/mongodb/index"); +// const mongodb = require("./models/mongodb/index"); +require("./plugins/sql/init"); const express = require("express"); const { createServer } = require("http"); const app = express(); diff --git a/config-class.js b/config-class.js index 32f14eee..d0c2158c 100644 --- a/config-class.js +++ b/config-class.js @@ -22,18 +22,18 @@ function generateUidFromGuid(iGuid) { return `2.25.${bigInteger.toString()}`; //Output the previus parsed integer as string by adding `2.25.` as prefix } -class MongoDbConfig { - constructor() { - this.dbName = env.get("MONGODB_NAME").default("raccoon").asString(); - this.hosts = env.get("MONGODB_HOSTS").required().asJsonArray(); - this.ports = env.get("MONGODB_PORTS").required().asJsonArray(); - this.user = env.get("MONGODB_USER").default("").asString(); - this.password = env.get("MONGODB_PASSWORD").default("").asString(); - this.authSource = env.get("MONGODB_AUTH_SOURCE").default("admin").asString(); - this.urlOptions = env.get("MONGODB_OPTIONS").default("").asString(); - this.isShardingMode = env.get("MONGODB_IS_SHARDING_MODE").default("false").asBool(); - } -} +/** + * @type {import("sequelize").Options} + */ +const SqlDbConfig = { + host: env.get("SQL_HOST").default("127.0.0.1").asString(), + port: env.get("SQL_PORT").default("5432").asString(), + database: env.get("SQL_DB").default("raccoon").asString(), + dialect: env.get("SQL_TYPE").default("postgres").asString(), + username: env.get("SQL_USERNAME").default("postgres").asString(), + password: env.get("SQL_PASSWORD").default("postgres").asString() +}; + class ServerConfig { constructor() { @@ -53,71 +53,71 @@ class DicomWebConfig { } } -class DicomDimseConfig { - constructor(mongodbConfig, serverConfig, dicomWebConfig) { - /** @type { MongoDbConfig } */ - this.mongodbConfig = mongodbConfig; - /** @type { ServerConfig } */ - this.serverConfig = serverConfig; - /** @type { DicomWebConfig } */ - this.dicomWebConfig = dicomWebConfig; - this.enableDimse = env.get("ENABLE_DIMSE").default("true").asBool(); - - if (this.enableDimse) { - this.dcm4cheQrscpArgv = env.get("DCM4CHE_QRSCP_COMMAND").required().asJsonArray(); - this.generateDimseJsonConfig(); - this.replacePathInArgv(); - } - } - - generateDimseJsonConfig() { - let stowUrlObj = new URL(`http://${this.serverConfig.host}`); - stowUrlObj.port = this.serverConfig.port; - stowUrlObj.pathname = "dicom-web/studies"; - - let dimseConfig = { - mongodb: { - hosts: this.mongodbConfig.hosts, - ports: this.mongodbConfig.ports, - username: this.mongodbConfig.user, - password: this.mongodbConfig.password, - authSource: this.mongodbConfig.authSource, - database: this.mongodbConfig.dbName - }, - raccoon: { - dicomStoreRoot: this.dicomWebConfig.storeRootPath, - raccoonUploadScriptPath: path.join(__dirname, "./local/dicom-uploader-stow.js"), - mode: "STOW", - stowUrl: stowUrlObj.href - } - }; - fs.writeFileSync(path.join(__dirname, "./config/raccoon-dimse-app.json"), JSON.stringify(dimseConfig)); - } +// class DicomDimseConfig { +// constructor(mongodbConfig, serverConfig, dicomWebConfig) { +// /** @type { MongoDbConfig } */ +// this.mongodbConfig = mongodbConfig; +// /** @type { ServerConfig } */ +// this.serverConfig = serverConfig; +// /** @type { DicomWebConfig } */ +// this.dicomWebConfig = dicomWebConfig; +// this.enableDimse = env.get("ENABLE_DIMSE").default("true").asBool(); + +// if (this.enableDimse) { +// this.dcm4cheQrscpArgv = env.get("DCM4CHE_QRSCP_COMMAND").required().asJsonArray(); +// this.generateDimseJsonConfig(); +// this.replacePathInArgv(); +// } +// } + +// generateDimseJsonConfig() { +// let stowUrlObj = new URL(`http://${this.serverConfig.host}`); +// stowUrlObj.port = this.serverConfig.port; +// stowUrlObj.pathname = "dicom-web/studies"; + +// let dimseConfig = { +// mongodb: { +// hosts: this.mongodbConfig.hosts, +// ports: this.mongodbConfig.ports, +// username: this.mongodbConfig.user, +// password: this.mongodbConfig.password, +// authSource: this.mongodbConfig.authSource, +// database: this.mongodbConfig.dbName +// }, +// raccoon: { +// dicomStoreRoot: this.dicomWebConfig.storeRootPath, +// raccoonUploadScriptPath: path.join(__dirname, "./local/dicom-uploader-stow.js"), +// mode: "STOW", +// stowUrl: stowUrlObj.href +// } +// }; +// fs.writeFileSync(path.join(__dirname, "./config/raccoon-dimse-app.json"), JSON.stringify(dimseConfig)); +// } - replacePathInArgv() { - for(let i = 0 ; i < this.dcm4cheQrscpArgv.length ; i++) { - this.dcm4cheQrscpArgv[i] = this.dcm4cheQrscpArgv[i].replace(/{project}/gm, __dirname); - } - } - - getPort() { - let bindArgIndex = this.dcm4cheQrscpArgv.findIndex(v => v === "-b"); - /** @type {string} */ - let bindInfo = this.dcm4cheQrscpArgv[bindArgIndex + 1]; - return bindInfo.split(":").pop(); - } - - getAeTitle() { - let bindArgIndex = this.dcm4cheQrscpArgv.findIndex(v => v === "-b"); - /** @type {string} */ - let bindInfo = this.dcm4cheQrscpArgv[bindArgIndex + 1]; - - let aeTitleAndIp = bindInfo.split(":").shift(); - let aeTitle = aeTitleAndIp.includes("@") ? aeTitleAndIp.split("@").shift() : aeTitleAndIp; - return aeTitle; - } - -} +// replacePathInArgv() { +// for(let i = 0 ; i < this.dcm4cheQrscpArgv.length ; i++) { +// this.dcm4cheQrscpArgv[i] = this.dcm4cheQrscpArgv[i].replace(/{project}/gm, __dirname); +// } +// } + +// getPort() { +// let bindArgIndex = this.dcm4cheQrscpArgv.findIndex(v => v === "-b"); +// /** @type {string} */ +// let bindInfo = this.dcm4cheQrscpArgv[bindArgIndex + 1]; +// return bindInfo.split(":").pop(); +// } + +// getAeTitle() { +// let bindArgIndex = this.dcm4cheQrscpArgv.findIndex(v => v === "-b"); +// /** @type {string} */ +// let bindInfo = this.dcm4cheQrscpArgv[bindArgIndex + 1]; + +// let aeTitleAndIp = bindInfo.split(":").shift(); +// let aeTitle = aeTitleAndIp.includes("@") ? aeTitleAndIp.split("@").shift() : aeTitleAndIp; +// return aeTitle; +// } + +// } class FhirConfig { constructor() { @@ -129,21 +129,23 @@ class FhirConfig { class RaccoonConfig { constructor() { - this.mongoDbConfig = new MongoDbConfig(); + this.sqlDbConfig = SqlDbConfig; this.serverConfig = new ServerConfig(); this.dicomWebConfig = new DicomWebConfig(); - this.dicomDimseConfig = new DicomDimseConfig(this.mongoDbConfig, this.serverConfig, this.dicomWebConfig); + // this.dicomDimseConfig = new DicomDimseConfig(this.mongoDbConfig, this.serverConfig, this.dicomWebConfig); this.fhirConfig = new FhirConfig(); /** @type {string} */ this.mediaStorageUID = generateUidFromGuid( - uuid.v5(this.mongoDbConfig.dbName, NAME_SPACE) + uuid.v5(this.sqlDbConfig.database, NAME_SPACE) ); /** @type {string} */ - this.mediaStorageID = this.mongoDbConfig.dbName; + this.mediaStorageID = this.sqlDbConfig.database; + + this.aeTitle = this.dicomWebConfig.aeTitle; + // this.aeTitle = this.dicomDimseConfig.enableDimse ? this.dicomDimseConfig.getAeTitle() : this.dicomWebConfig.aeTitle; - this.aeTitle = this.dicomDimseConfig.enableDimse ? this.dicomDimseConfig.getAeTitle() : this.dicomWebConfig.aeTitle; if (!this.aeTitle) throw new Error("Missing required config `aeTitle`"); } diff --git a/package-lock.json b/package-lock.json index 9b214fdb..def24372 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "commander": "^10.0.1", "compression": "^1.7.4", "connect-mongo": "^4.6.0", + "connect-session-sequelize": "^7.1.7", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dicom-parser": "^1.8.13", @@ -43,10 +44,13 @@ "passport": "^0.6.0", "passport-local": "^1.0.0", "path-match": "^1.2.4", + "pg": "^8.11.1", + "pg-hstore": "^2.3.4", "regexparam": "^2.0.1", "request-compose": "^2.1.6", "request-multipart": "^1.0.0", "run-script-os": "^1.1.6", + "sequelize": "^6.32.1", "sharp": "^0.30.4", "shorthash2": "^1.0.3", "uuid": "^9.0.0", @@ -1328,6 +1332,14 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -1340,6 +1352,11 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, "node_modules/@types/node": { "version": "17.0.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", @@ -1351,6 +1368,11 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "node_modules/@types/validator": { + "version": "13.7.17", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.17.tgz", + "integrity": "sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -1862,6 +1884,14 @@ "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==" }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -2504,6 +2534,20 @@ "mongodb": "^4.1.0" } }, + "node_modules/connect-session-sequelize": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/connect-session-sequelize/-/connect-session-sequelize-7.1.7.tgz", + "integrity": "sha512-Wqq7rg0w+9bOVs6jC0nhZnssXJ3+iKNlDVWn2JfBuBPoY7oYaxzxfBKeUYrX6dHt3OWEWbZV6LJvapwi76iBQQ==", + "dependencies": { + "debug": "^4.1.1" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "sequelize": ">= 6.1.0" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -3185,6 +3229,11 @@ "node": ">=4" } }, + "node_modules/dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -4472,6 +4521,14 @@ "node": ">=8" } }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "engines": [ + "node >= 0.4.0" + ] + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6318,6 +6375,11 @@ "node": ">=6" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6543,6 +6605,108 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "node_modules/pg": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.1.tgz", + "integrity": "sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.6.1", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz", + "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==" + }, + "node_modules/pg-hstore": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.4.tgz", + "integrity": "sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==", + "dependencies": { + "underscore": "^1.13.1" + }, + "engines": { + "node": ">= 0.8.x" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/pgpass/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -6576,6 +6740,41 @@ "node": ">=8" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prebuild-install": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", @@ -7129,6 +7328,11 @@ "node": ">=4" } }, + "node_modules/retry-as-promised": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", + "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==" + }, "node_modules/rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -7194,9 +7398,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -7248,6 +7452,83 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/sequelize": { + "version": "6.32.1", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.32.1.tgz", + "integrity": "sha512-3Iv0jruv57Y0YvcxQW7BE56O7DC1BojcfIrqh6my+IQwde+9u/YnuYHzK+8kmZLhLvaziRT1eWu38nh9yVwn/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.4", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.0", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.1", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/sequelize/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -8026,6 +8307,11 @@ "node": ">=0.6" } }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -8142,6 +8428,11 @@ "node": ">= 0.8" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -8260,7 +8551,6 @@ "version": "13.9.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", - "dev": true, "engines": { "node": ">= 0.10" } @@ -8317,6 +8607,14 @@ "node": ">= 0.10.0" } }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -8423,7 +8721,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, "engines": { "node": ">=0.4" } @@ -9694,6 +9991,14 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, + "@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "requires": { + "@types/ms": "*" + } + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -9706,6 +10011,11 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, "@types/node": { "version": "17.0.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", @@ -9717,6 +10027,11 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "@types/validator": { + "version": "13.7.17", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.17.tgz", + "integrity": "sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==" + }, "@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -10124,6 +10439,11 @@ "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==" }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -10654,6 +10974,14 @@ "kruptein": "^3.0.0" } }, + "connect-session-sequelize": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/connect-session-sequelize/-/connect-session-sequelize-7.1.7.tgz", + "integrity": "sha512-Wqq7rg0w+9bOVs6jC0nhZnssXJ3+iKNlDVWn2JfBuBPoY7oYaxzxfBKeUYrX6dHt3OWEWbZV6LJvapwi76iBQQ==", + "requires": { + "debug": "^4.1.1" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -11164,6 +11492,11 @@ } } }, + "dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -12153,6 +12486,11 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -13532,6 +13870,11 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -13705,6 +14048,83 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "pg": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.1.tgz", + "integrity": "sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-cloudflare": "^1.1.1", + "pg-connection-string": "^2.6.1", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "pg-connection-string": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz", + "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==" + }, + "pg-hstore": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.4.tgz", + "integrity": "sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==", + "requires": { + "underscore": "^1.13.1" + } + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "requires": {} + }, + "pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + }, + "dependencies": { + "split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + } + } + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -13726,6 +14146,29 @@ "find-up": "^4.0.0" } }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "prebuild-install": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", @@ -14162,6 +14605,11 @@ "dev": true, "peer": true }, + "retry-as-promised": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", + "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==" + }, "rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -14200,9 +14648,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } @@ -14249,6 +14697,41 @@ } } }, + "sequelize": { + "version": "6.32.1", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.32.1.tgz", + "integrity": "sha512-3Iv0jruv57Y0YvcxQW7BE56O7DC1BojcfIrqh6my+IQwde+9u/YnuYHzK+8kmZLhLvaziRT1eWu38nh9yVwn/g==", + "requires": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.4", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.0", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.1", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, + "sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==" + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -14848,6 +15331,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -14931,6 +15419,11 @@ "random-bytes": "~1.0.0" } }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -15038,8 +15531,7 @@ "validator": { "version": "13.9.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", - "dev": true + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" }, "vary": { "version": "1.1.2", @@ -15081,6 +15573,14 @@ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" }, + "wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "requires": { + "@types/node": "*" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -15156,8 +15656,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "3.2.2", diff --git a/package.json b/package.json index e93c3bbf..acfe9916 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "commander": "^10.0.1", "compression": "^1.7.4", "connect-mongo": "^4.6.0", + "connect-session-sequelize": "^7.1.7", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dicom-parser": "^1.8.13", @@ -96,10 +97,13 @@ "passport": "^0.6.0", "passport-local": "^1.0.0", "path-match": "^1.2.4", + "pg": "^8.11.1", + "pg-hstore": "^2.3.4", "regexparam": "^2.0.1", "request-compose": "^2.1.6", "request-multipart": "^1.0.0", "run-script-os": "^1.1.6", + "sequelize": "^6.32.1", "sharp": "^0.30.4", "shorthash2": "^1.0.3", "uuid": "^9.0.0", diff --git a/plugins/sql/init.js b/plugins/sql/init.js new file mode 100644 index 00000000..0c0e3f8b --- /dev/null +++ b/plugins/sql/init.js @@ -0,0 +1,23 @@ +const { PersonNameModel } = require("./models/personName.model"); +const { PatientModel } = require("./models/patient.model"); +const sequelizeInstance = require("./instance"); + +async function init() { + try { + await sequelizeInstance.authenticate(); + + PatientModel.belongsTo(PersonNameModel, { + foreignKey: "x00100010" + }); + + await sequelizeInstance.sync({}); + } catch (e) { + console.error('Unable to connect to the database:', e); + process.exit(1); + } + +} + +module.exports = (() => init())(); + + diff --git a/plugins/sql/instance.js b/plugins/sql/instance.js new file mode 100644 index 00000000..197eb0e7 --- /dev/null +++ b/plugins/sql/instance.js @@ -0,0 +1,9 @@ +const { raccoonConfig } = require("@root/config-class"); +const { Sequelize } = require("sequelize"); + +const sequelize = new Sequelize(raccoonConfig.sqlDbConfig); + +/** + * @type {Sequelize} + */ +module.exports = sequelize; \ No newline at end of file diff --git a/plugins/sql/models/dicom-json-model.js b/plugins/sql/models/dicom-json-model.js new file mode 100644 index 00000000..84218706 --- /dev/null +++ b/plugins/sql/models/dicom-json-model.js @@ -0,0 +1,41 @@ +const _ = require("lodash"); + +const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); + + +class SqlDicomJsonModel extends DicomJsonModel { + constructor(dicomJson) { + super(dicomJson); + } + + async storeToDb(dicomFileSaveInfo) { + let dicomJsonClone = _.cloneDeep(this.dicomJson); + try { + let mediaStorage = this.getMediaStorageInfo(); + _.merge(dicomJsonClone, this.uidObj); + _.merge(dicomJsonClone, { + studyPath: dicomFileSaveInfo.studyPath, + seriesPath: dicomFileSaveInfo.seriesPath, + instancePath: dicomFileSaveInfo.relativePath + }); + _.merge(dicomJsonClone, mediaStorage); + + delete dicomJsonClone.sopClass; + delete dicomJsonClone.sopInstanceUID; + + await Promise.all([ + this.storePatientCollection(dicomJsonClone) + ]); + } catch(e) { + throw e; + } + } + + async storePatientCollection(dicomJson) { + console.log(dicomJson); + console.log("TODO: Store Patient"); + } +} + + +module.exports.SqlDicomJsonModel = SqlDicomJsonModel; \ No newline at end of file diff --git a/plugins/sql/models/patient.model.js b/plugins/sql/models/patient.model.js new file mode 100644 index 00000000..1cf9d10b --- /dev/null +++ b/plugins/sql/models/patient.model.js @@ -0,0 +1,51 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@root/plugins/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class PatientModel extends Model {}; + +PatientModel.init({ + "x00100010": { + type: DataTypes.INTEGER + }, + "x00100020": { + type: vrTypeMapping.LO + }, + "x00100021": { + type: vrTypeMapping.LO + }, + "x00100030": { + type: vrTypeMapping.DA + }, + "x00100032": { + type: vrTypeMapping.TM + }, + "x00100040": { + type: vrTypeMapping.CS + }, + "x00101001": { + type: vrTypeMapping.PN + }, + "x00102160": { + type: vrTypeMapping.SH + }, + "x00104000": { + type: vrTypeMapping.LT + }, + "x00880130": { + type: vrTypeMapping.SH + }, + "x00880140": { + type: vrTypeMapping.UI + }, + "json": { + type: DataTypes.JSON + } +}, { + sequelize: sequelizeInstance, + modelName: "Patient", + tableName: "Patient", + freezeTableName: true +}); + +module.exports.PatientModel = PatientModel; diff --git a/plugins/sql/models/personName.model.js b/plugins/sql/models/personName.model.js new file mode 100644 index 00000000..2bdf9bc9 --- /dev/null +++ b/plugins/sql/models/personName.model.js @@ -0,0 +1,31 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@root/plugins/sql/instance"); + + +class PersonNameModel extends Model {} +PersonNameModel.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + alphabetic: { + type: DataTypes.STRING + }, + ideographic: { + type: DataTypes.STRING, + allowNull: true + }, + phonetic: { + type: DataTypes.STRING, + allowNull: true + } +}, { + sequelize: sequelizeInstance, + modelName: "PersonName", + tableName: "PersonName", + freezeTableName: true +}); + + +module.exports.PersonNameModel = PersonNameModel; diff --git a/plugins/sql/vrTypeMapping.js b/plugins/sql/vrTypeMapping.js new file mode 100644 index 00000000..65f19dc2 --- /dev/null +++ b/plugins/sql/vrTypeMapping.js @@ -0,0 +1,38 @@ +const { DataTypes } = require("sequelize"); + +const vrTypeMapping = { + "AE": DataTypes.STRING(16+1), + "AS": DataTypes.STRING(4+1), + "CS": DataTypes.STRING(16+1), + "DA": DataTypes.DATEONLY, + "DS": DataTypes.STRING(16+1), + "DT": DataTypes.DATE, + "FT": DataTypes.FLOAT, + "FD": DataTypes.DOUBLE, + "IS": DataTypes.STRING(12+1), + "LO": DataTypes.STRING(64+1), + "LT": DataTypes.STRING(10240+1), + "OB": DataTypes.TEXT, + "OD": DataTypes.TEXT, + "OF": DataTypes.TEXT, + "OL": DataTypes.TEXT, + "OV": DataTypes.TEXT, + "OW": DataTypes.TEXT, + "PN": DataTypes.INTEGER, // foreign key + "SH": DataTypes.STRING(16+1), + "SL": DataTypes.INTEGER, + "SS": DataTypes.SMALLINT, + "ST": DataTypes.STRING(1024+1), + "SV": DataTypes.BIGINT, + "TM": DataTypes.DECIMAL, + "UC": DataTypes.TEXT("long"), + "UI": DataTypes.STRING(64+1), + "UL": DataTypes.INTEGER.UNSIGNED, + "UR": DataTypes.TEXT("long"), + "US": DataTypes.SMALLINT.UNSIGNED, + "UT": DataTypes.TEXT("long"), + "UV": DataTypes.BIGINT.UNSIGNED +}; + + +module.exports.vrTypeMapping = vrTypeMapping; \ No newline at end of file diff --git a/plugins/store-sql/index.js b/plugins/store-sql/index.js new file mode 100644 index 00000000..53df6585 --- /dev/null +++ b/plugins/store-sql/index.js @@ -0,0 +1,55 @@ +const { performance } = require("node:perf_hooks"); +const errorResponseMessage = require("@root/utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StowRsRequestMultipartParser } = require("@root/api/dicom-web/controller/STOW-RS/service/request-multipart-parser"); +const { SqlStowRsService } = require("./service/stow-rs.service"); + + +/** + * To store DICOM instance + * 1. we parse multipart request to get file info that user upload + * 2. parse DICOM to JSON and store DICOM file from step 1 + * 3. parse DICOM json model to FHIR (Patient, Endpoint, ImagingStudy) + * 4. upload FHIR to FHIR server + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let startSTOWTime = performance.now(); + let retCode; + let storeMessage; + let apiLogger = new ApiLogger(req, "STOW-RS"); + apiLogger.addTokenValue(); + + try { + let requestMultipartParser = new StowRsRequestMultipartParser(req); + let multipartParseResult = await requestMultipartParser.parse(); + + if (multipartParseResult.status) { + let stowRsService = new SqlStowRsService(req, multipartParseResult.multipart.files); + let storeInstancesResult = await stowRsService.storeInstances(); + + retCode = storeInstancesResult.code; + storeMessage = storeInstancesResult.responseMessage; + } + let endSTOWTime = performance.now(); + let elapsedTime = (endSTOWTime - startSTOWTime).toFixed(3); + apiLogger.logger.info(`Finished STOW-RS, elapsed time: ${elapsedTime} ms`); + + res.writeHead(retCode, { + "Content-Type": "application/dicom" + }); + + return res.end(JSON.stringify(storeMessage)); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + let errorMessage = + errorResponseMessage.getInternalServerErrorMessage(errorStr); + res.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return res.end(JSON.stringify(errorMessage)); + } +}; diff --git a/plugins/store-sql/service/stow-rs.service.js b/plugins/store-sql/service/stow-rs.service.js new file mode 100644 index 00000000..1541d4f4 --- /dev/null +++ b/plugins/store-sql/service/stow-rs.service.js @@ -0,0 +1,62 @@ +const _ = require("lodash"); + +const { DicomJsonParser } = require("@models/DICOM/dicom-json-parser"); +const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); +const { DicomFileSaver } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-file-saver"); +const { SqlDicomJsonModel: DicomJsonModel } = require("../../sql/models/dicom-json-model"); + +class SqlStowRsService extends StowRsService { + /** + * @param {import('express').Request} req + * @param {import('formidable').File[]} uploadFiles + */ + constructor(req, uploadFiles) { + super(req, uploadFiles); + } + + /** + * + * @param {import("formidable").File} file + */ + async storeInstance(file) { + let dicomJsonParser = new DicomJsonParser(); + let dicomJson = await dicomJsonParser.parseFromFilename(file.filepath); + + let dicomJsonModel = new DicomJsonModel(dicomJson); + dicomJsonModel.setMinifyDicomJsonAndTempBigValueTags(); + dicomJsonModel.setUidObj(); + + let isSameStudyIDStatus = this.isSameStudyID_(this.responseMessage); + if (!isSameStudyIDStatus) { + this.responseCode = 409; + } + + // let dicomJsonBinaryDataModel = new DicomJsonBinaryDataModel(dicomJsonModel); + // await dicomJsonBinaryDataModel.storeAllBinaryDataToFileAndDb(); + // dicomJsonBinaryDataModel.replaceAllBinaryToURI(); + + let dicomFileSaver = new DicomFileSaver(file, dicomJsonModel); + let dicomFileSaveInfo = await dicomFileSaver.saveAndGetSaveInfo(); + + await dicomJsonModel.saveMetadataToFile(dicomFileSaveInfo.fullPath); + + await dicomJsonModel.storeToDb(dicomFileSaveInfo); + + let retrieveUrlObj = this.getRetrieveUrl(dicomJsonModel.uidObj); + this.responseMessage["00081190"].Value.push(retrieveUrlObj.study); + this.responseMessage["00081190"].Value = _.uniq(this.responseMessage["00081190"].Value); + + let sopSeq = this.getSOPSeq(dicomJsonModel.uidObj.sopClass, dicomJsonModel.uidObj.sopInstanceUID); + _.set(sopSeq, "00081190.vr", "UT"); + _.set(sopSeq, "00081190.Value", [retrieveUrlObj.instance]); + this.responseMessage["00081199"]["Value"].push(sopSeq); + + return { + dicomJsonModel, + dicomFileSaveInfo + }; + } +} + + +module.exports.SqlStowRsService = SqlStowRsService; \ No newline at end of file diff --git a/server.js b/server.js index e9674c8b..4d5118d9 100644 --- a/server.js +++ b/server.js @@ -8,8 +8,8 @@ const cookieParser = require("cookie-parser"); const compress = require("compression"); const cors = require("cors"); const os = require("os"); -const mongoose = require("mongoose"); -const MongoStore = require("connect-mongo"); +const SequelizeStore = require("connect-session-sequelize")(session.Store); +const sequelizeInstance = require("./plugins/sql/instance"); const passport = require("passport"); const { raccoonConfig } = require("./config-class"); @@ -50,9 +50,8 @@ app.use( httpOnly: true, maxAge: 60 * 60 * 1000 }, - store: MongoStore.create({ - client: mongoose.connection.getClient(), - dbName: raccoonConfig.mongoDbConfig.dbName + store: new SequelizeStore({ + db: sequelizeInstance }) }) ); @@ -85,23 +84,23 @@ if (osPlatform.includes("linux")) { // #region DIMSE -(async () => { - if (raccoonConfig.dicomDimseConfig.enableDimse) { - const { java } = require("./models/DICOM/dcm4che/java-instance"); - let dcmQrScpClass = await java.importClassAsync("org.dcm4che3.tool.dcmqrscp.DcmQRSCP"); - const net = require("net"); - let checkPortServer = net.createServer() - .once("listening", async function () { - checkPortServer.close(); - await dcmQrScpClass.main(raccoonConfig.dicomDimseConfig.dcm4cheQrscpArgv); - }) - .once("error", function (err) { - if (err.code === "EADDRINUSE") { - console.log("QRSCP's port is already in use, please check is QRSCP running or another app running"); - } - }) - .listen(raccoonConfig.dicomDimseConfig.getPort()); - } -})(); +// (async () => { +// if (raccoonConfig.dicomDimseConfig.enableDimse) { +// const { java } = require("./models/DICOM/dcm4che/java-instance"); +// let dcmQrScpClass = await java.importClassAsync("org.dcm4che3.tool.dcmqrscp.DcmQRSCP"); +// const net = require("net"); +// let checkPortServer = net.createServer() +// .once("listening", async function () { +// checkPortServer.close(); +// await dcmQrScpClass.main(raccoonConfig.dicomDimseConfig.dcm4cheQrscpArgv); +// }) +// .once("error", function (err) { +// if (err.code === "EADDRINUSE") { +// console.log("QRSCP's port is already in use, please check is QRSCP running or another app running"); +// } +// }) +// .listen(raccoonConfig.dicomDimseConfig.getPort()); +// } +// })(); // #endregion \ No newline at end of file From 09bbc3831cf7a04bef5e004ac91549bb50ec442d Mon Sep 17 00:00:00 2001 From: chin Date: Mon, 31 Jul 2023 22:19:03 +0800 Subject: [PATCH 002/365] refactor: move SQL out of plugins and integration --- .../STOW-RS/service/dicom-jpeg-generator.js | 74 +++++++++++++++++++ .../service/request-multipart-parser.js | 47 ++++++++++++ .../STOW-RS}/service/stow-rs.service.js | 32 +++++++- .../controller/STOW-RS/storeInstance.js | 68 +++++++++++++++++ api-sql/dicom-web/stow-rs.route.js | 35 +++++++++ app.js | 2 +- .../models => models/sql}/dicom-json-model.js | 5 +- {plugins => models}/sql/init.js | 2 +- {plugins => models}/sql/instance.js | 0 .../sql/models/patient.model.js | 5 +- .../sql/models/personName.model.js | 2 +- models/sql/po/patient.po.js | 68 +++++++++++++++++ {plugins => models}/sql/vrTypeMapping.js | 0 plugins/store-sql/index.js | 55 -------------- routes.js | 2 +- server.js | 2 +- 16 files changed, 332 insertions(+), 67 deletions(-) create mode 100644 api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js create mode 100644 api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js rename {plugins/store-sql => api-sql/dicom-web/controller/STOW-RS}/service/stow-rs.service.js (67%) create mode 100644 api-sql/dicom-web/controller/STOW-RS/storeInstance.js create mode 100644 api-sql/dicom-web/stow-rs.route.js rename {plugins/sql/models => models/sql}/dicom-json-model.js (85%) rename {plugins => models}/sql/init.js (90%) rename {plugins => models}/sql/instance.js (100%) rename {plugins => models}/sql/models/patient.model.js (88%) rename {plugins => models}/sql/models/personName.model.js (90%) create mode 100644 models/sql/po/patient.po.js rename {plugins => models}/sql/vrTypeMapping.js (100%) delete mode 100644 plugins/store-sql/index.js diff --git a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js new file mode 100644 index 00000000..2b5b4b3f --- /dev/null +++ b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js @@ -0,0 +1,74 @@ +const fs = require("fs"); +const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); +const colorette = require("colorette"); +const { DicomJpegGenerator } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator"); +/** + * @typedef JsDcm2JpegTask + * @property {Dcm2JpgExecutor$Dcm2JpgOptions} jsDcm2Jpeg + * @property {string} jpegFilename + */ + +class SqlDicomJpegGenerator extends DicomJpegGenerator { + /** + * + * @param {import("../../../../../models/DICOM/dicom-json-model").DicomJsonModel} dicomJsonModel + * @param {string} dicomInstanceFilename + */ + constructor(dicomJsonModel, dicomInstanceFilename) { + super(dicomJsonModel, dicomInstanceFilename); + } + + + + /** + * @private + */ + async insertStartTask_() { + let startTaskObj = { + studyUID: this.dicomJsonModel.uidObj.studyUID, + seriesUID: this.dicomJsonModel.uidObj.seriesUID, + instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + status: false, + message: "processing", + taskTime: new Date(), + finishedTime: null, + fileSize: `${(fs.statSync(this.dicomInstanceFilename).size / 1024 / 1024).toFixed(3)}MB` + }; + + + } + + /** + * @private + */ + async insertEndTask_() { + let endTaskObj = { + studyUID: this.dicomJsonModel.uidObj.studyUID, + seriesUID: this.dicomJsonModel.uidObj.seriesUID, + instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + status: true, + message: "generated", + finishedTime: new Date() + }; + + } + + /** + * @private + * @param {string} message + */ + async insertErrorTask_(message) { + let errorTaskObj = { + studyUID: this.dicomJsonModel.uidObj.studyUID, + seriesUID: this.dicomJsonModel.uidObj.seriesUID, + instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + status: false, + message: message, + finishedTime: new Date() + }; + + } + +} + +module.exports.SqlDicomJpegGenerator = SqlDicomJpegGenerator; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js b/api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js new file mode 100644 index 00000000..c10c8af3 --- /dev/null +++ b/api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js @@ -0,0 +1,47 @@ +const formidable = require("formidable"); +const path = require("path"); +const _ = require("lodash"); + +class StowRsRequestMultipartParser { + /** + * @param {import('express').Request} req + */ + constructor(req) { + this.request = req; + } + + /** + * + * @return {Promise} + */ + async parse() { + return new Promise((resolve, reject) => { + new formidable.IncomingForm({ + uploadDir: path.join(process.cwd(), "/tempUploadFiles"), + maxFileSize: 100 * 1024 * 1024 * 1024, + multiples: true, + isGetBoundaryInData: true + }).parse(this.request, async (err, fields, files) => { + + if (err) { + return reject(err); + } + + let fileField = Object.keys(files).pop(); + let uploadFiles = files[fileField]; + if (!_.isArray(uploadFiles)) uploadFiles = [uploadFiles]; + + return resolve({ + status: true, + multipart: { + fields: fields, + files: uploadFiles + } + }); + + }); + }); + } +} + +module.exports.StowRsRequestMultipartParser = StowRsRequestMultipartParser; \ No newline at end of file diff --git a/plugins/store-sql/service/stow-rs.service.js b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js similarity index 67% rename from plugins/store-sql/service/stow-rs.service.js rename to api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js index 1541d4f4..1ad91a31 100644 --- a/plugins/store-sql/service/stow-rs.service.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -3,7 +3,8 @@ const _ = require("lodash"); const { DicomJsonParser } = require("@models/DICOM/dicom-json-parser"); const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); const { DicomFileSaver } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-file-saver"); -const { SqlDicomJsonModel: DicomJsonModel } = require("../../sql/models/dicom-json-model"); +const { SqlDicomJsonModel: DicomJsonModel } = require("@models/sql/dicom-json-model"); +const { SqlDicomJpegGenerator: DicomJpegGenerator } = require("./dicom-jpeg-generator"); class SqlStowRsService extends StowRsService { /** @@ -14,6 +15,35 @@ class SqlStowRsService extends StowRsService { super(req, uploadFiles); } + async storeInstances() { + for (let i = 0; i < this.uploadFiles.length; i++) { + + let currentFile = this.uploadFiles[i]; + + let { + dicomJsonModel, + dicomFileSaveInfo + } = await this.storeInstance(currentFile); + + + //sync DICOM to FHIR + // if (isSyncToFhir) { + // let dicomFhirService = new DicomFhirService(this.request, dicomJsonModel); + // await dicomFhirService.initDicomFhirConverter(); + // await dicomFhirService.postDicomToFhirServerAndStoreLog(); + // } + + //generate JPEG + // let dicomJpegGenerator = new DicomJpegGenerator(dicomJsonModel, dicomFileSaveInfo.instancePath); + // dicomJpegGenerator.generateAllFrames(); + } + + return { + code: this.responseCode, + responseMessage: this.responseMessage + }; + } + /** * * @param {import("formidable").File} file diff --git a/api-sql/dicom-web/controller/STOW-RS/storeInstance.js b/api-sql/dicom-web/controller/STOW-RS/storeInstance.js new file mode 100644 index 00000000..cec91a7c --- /dev/null +++ b/api-sql/dicom-web/controller/STOW-RS/storeInstance.js @@ -0,0 +1,68 @@ +const { performance } = require("node:perf_hooks"); +const errorResponseMessage = require("@root/utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); +const { StowRsRequestMultipartParser } = require("./service/request-multipart-parser"); +const { SqlStowRsService: StowRsService } = require("./service/stow-rs.service"); + +class StoreInstanceController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let startSTOWTime = performance.now(); + let retCode; + let storeMessage; + let apiLogger = new ApiLogger(this.request, "STOW-RS"); + apiLogger.addTokenValue(); + + try { + let requestMultipartParser = new StowRsRequestMultipartParser(this.request); + let multipartParseResult = await requestMultipartParser.parse(); + + if (multipartParseResult.status) { + let stowRsService = new StowRsService(this.request, multipartParseResult.multipart.files); + let storeInstancesResult = await stowRsService.storeInstances(); + + retCode = storeInstancesResult.code; + storeMessage = storeInstancesResult.responseMessage; + } + let endSTOWTime = performance.now(); + let elapsedTime = (endSTOWTime - startSTOWTime).toFixed(3); + apiLogger.logger.info(`Finished STOW-RS, elapsed time: ${elapsedTime} ms`); + + this.response.writeHead(retCode, { + "Content-Type": "application/dicom" + }); + + return this.response.end(JSON.stringify(storeMessage)); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + let errorMessage = + errorResponseMessage.getInternalServerErrorMessage(errorStr); + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(errorMessage)); + } + } +} + + +/** + * To store DICOM instance + * 1. we parse multipart request to get file info that user upload + * 2. parse DICOM to JSON and store DICOM file from step 1 + * 3. parse DICOM json model to FHIR (Patient, Endpoint, ImagingStudy) + * 4. upload FHIR to FHIR server + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new StoreInstanceController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/stow-rs.route.js b/api-sql/dicom-web/stow-rs.route.js new file mode 100644 index 00000000..73b80eee --- /dev/null +++ b/api-sql/dicom-web/stow-rs.route.js @@ -0,0 +1,35 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi } = require("@root/api/validator"); +const router = express(); + +//#region STOW-RS + +/** + * @openapi + * /dicom-web/studies: + * post: + * tags: + * - STOW-RS + * description: store DICOM instance + * requestBody: + * content: + * multipart/related: + * schema: + * type: object + * properties: + * file: + * type: string + * format: binary + * encoding: + * file: + * contentType: application/dicom; + * responses: + * "200": + * description: The DICOM instance store successfully + */ +router.post("/studies", require("./controller/STOW-RS/storeInstance")); + +//#endregion + +module.exports = router; \ No newline at end of file diff --git a/app.js b/app.js index 5833d74c..e1674a52 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,5 @@ // const mongodb = require("./models/mongodb/index"); -require("./plugins/sql/init"); +require("./models/sql/init"); const express = require("express"); const { createServer } = require("http"); const app = express(); diff --git a/plugins/sql/models/dicom-json-model.js b/models/sql/dicom-json-model.js similarity index 85% rename from plugins/sql/models/dicom-json-model.js rename to models/sql/dicom-json-model.js index 84218706..5ad6e706 100644 --- a/plugins/sql/models/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -1,6 +1,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { PatientPersistentObject } = require("./po/patient.po"); class SqlDicomJsonModel extends DicomJsonModel { @@ -32,8 +33,8 @@ class SqlDicomJsonModel extends DicomJsonModel { } async storePatientCollection(dicomJson) { - console.log(dicomJson); - console.log("TODO: Store Patient"); + let patientPo = new PatientPersistentObject(dicomJson); + let patient = await patientPo.createPatient(); } } diff --git a/plugins/sql/init.js b/models/sql/init.js similarity index 90% rename from plugins/sql/init.js rename to models/sql/init.js index 0c0e3f8b..b673d28b 100644 --- a/plugins/sql/init.js +++ b/models/sql/init.js @@ -10,7 +10,7 @@ async function init() { foreignKey: "x00100010" }); - await sequelizeInstance.sync({}); + await sequelizeInstance.sync({force: true}); } catch (e) { console.error('Unable to connect to the database:', e); process.exit(1); diff --git a/plugins/sql/instance.js b/models/sql/instance.js similarity index 100% rename from plugins/sql/instance.js rename to models/sql/instance.js diff --git a/plugins/sql/models/patient.model.js b/models/sql/models/patient.model.js similarity index 88% rename from plugins/sql/models/patient.model.js rename to models/sql/models/patient.model.js index 1cf9d10b..1737f7bc 100644 --- a/plugins/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -1,5 +1,5 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@root/plugins/sql/instance"); +const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); class PatientModel extends Model {}; @@ -23,9 +23,6 @@ PatientModel.init({ "x00100040": { type: vrTypeMapping.CS }, - "x00101001": { - type: vrTypeMapping.PN - }, "x00102160": { type: vrTypeMapping.SH }, diff --git a/plugins/sql/models/personName.model.js b/models/sql/models/personName.model.js similarity index 90% rename from plugins/sql/models/personName.model.js rename to models/sql/models/personName.model.js index 2bdf9bc9..2ed7e9af 100644 --- a/plugins/sql/models/personName.model.js +++ b/models/sql/models/personName.model.js @@ -1,5 +1,5 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@root/plugins/sql/instance"); +const sequelizeInstance = require("@models/sql/instance"); class PersonNameModel extends Model {} diff --git a/models/sql/po/patient.po.js b/models/sql/po/patient.po.js new file mode 100644 index 00000000..dfcd6da2 --- /dev/null +++ b/models/sql/po/patient.po.js @@ -0,0 +1,68 @@ +const moment = require("moment"); +const _ = require("lodash"); +const { PersonNameModel } = require("../models/personName.model"); +const { PatientModel } = require("../models/patient.model"); +const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); + + +class PatientPersistentObject { + constructor(dicomJson) { + this.json = {}; + Object.keys(tagsNeedStore.Patient).forEach(key => { + let value = _.get(dicomJson, key); + value ? _.set(this.json, key, value) : undefined; + }); + this.x00100010 = _.get(dicomJson, "00100010.Value.0", ""); + this.x00100020 = _.get(dicomJson, "00100020.Value.0", ""); + this.x00100021 = _.get(dicomJson, "00100021.Value.0", ""); + this.x00100030 = _.get(dicomJson, "00100030.Value.0", ""); + this.x00100032 = _.get(dicomJson, "00100032.Value.0", ""); + this.x00100040 = _.get(dicomJson, "00100040.Value.0", ""); + this.x00102160 = _.get(dicomJson, "00102160.Value.0", ""); + this.x00104000 = _.get(dicomJson, "00104000.Value.0", ""); + this.x00880130 = _.get(dicomJson, "00880130.Value.0", ""); + this.x00880140 = _.get(dicomJson, "00880140.Value.0", ""); + } + + async createPersonName() { + if (this.x00100010) { + return await PersonNameModel.create({ + alphabetic: _.get(this.x00100010, "Alphabetic", undefined), + ideographic: _.get(this.x00100010, "Ideographic", undefined), + phonetic: _.get(this.x00100010, "Phonetic", undefined) + }); + } + return undefined; + } + + async createPatient() { + let [patient, created] = await PatientModel.findOrCreate({ + where: { + x00100020: this.x00100020 + }, + defaults: { + json: this.json, + x00100020: this.x00100020, + x00100021: this.x00100021, + x00100030: this.x00100030 ? this.x00100030 : undefined, + x00100032: this.x00100032 ? Number(this.x00100032) : undefined, + x00100040: this.x00100040, + x00102160: this.x00102160, + x00104000: this.x00104000, + x00880130: this.x00880130, + x00880140: this.x00880140 + } + }); + + if (created) { + let personName = await this.createPersonName(); + patient.x00100010 = personName ? personName.id : undefined; + await patient.save(); + } + + return patient; + } + +} + +module.exports.PatientPersistentObject = PatientPersistentObject; \ No newline at end of file diff --git a/plugins/sql/vrTypeMapping.js b/models/sql/vrTypeMapping.js similarity index 100% rename from plugins/sql/vrTypeMapping.js rename to models/sql/vrTypeMapping.js diff --git a/plugins/store-sql/index.js b/plugins/store-sql/index.js deleted file mode 100644 index 53df6585..00000000 --- a/plugins/store-sql/index.js +++ /dev/null @@ -1,55 +0,0 @@ -const { performance } = require("node:perf_hooks"); -const errorResponseMessage = require("@root/utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { StowRsRequestMultipartParser } = require("@root/api/dicom-web/controller/STOW-RS/service/request-multipart-parser"); -const { SqlStowRsService } = require("./service/stow-rs.service"); - - -/** - * To store DICOM instance - * 1. we parse multipart request to get file info that user upload - * 2. parse DICOM to JSON and store DICOM file from step 1 - * 3. parse DICOM json model to FHIR (Patient, Endpoint, ImagingStudy) - * 4. upload FHIR to FHIR server - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let startSTOWTime = performance.now(); - let retCode; - let storeMessage; - let apiLogger = new ApiLogger(req, "STOW-RS"); - apiLogger.addTokenValue(); - - try { - let requestMultipartParser = new StowRsRequestMultipartParser(req); - let multipartParseResult = await requestMultipartParser.parse(); - - if (multipartParseResult.status) { - let stowRsService = new SqlStowRsService(req, multipartParseResult.multipart.files); - let storeInstancesResult = await stowRsService.storeInstances(); - - retCode = storeInstancesResult.code; - storeMessage = storeInstancesResult.responseMessage; - } - let endSTOWTime = performance.now(); - let elapsedTime = (endSTOWTime - startSTOWTime).toFixed(3); - apiLogger.logger.info(`Finished STOW-RS, elapsed time: ${elapsedTime} ms`); - - res.writeHead(retCode, { - "Content-Type": "application/dicom" - }); - - return res.end(JSON.stringify(storeMessage)); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - let errorMessage = - errorResponseMessage.getInternalServerErrorMessage(errorStr); - res.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return res.end(JSON.stringify(errorMessage)); - } -}; diff --git a/routes.js b/routes.js index a994920c..70fa56ea 100644 --- a/routes.js +++ b/routes.js @@ -19,7 +19,7 @@ module.exports = function (app) { loadAllPlugin(); - app.use("/dicom-web", require("./api/dicom-web/stow-rs.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/stow-rs.route")); app.use("/dicom-web", require("./api/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-metadata.route")); diff --git a/server.js b/server.js index 4d5118d9..fd685a37 100644 --- a/server.js +++ b/server.js @@ -9,7 +9,7 @@ const compress = require("compression"); const cors = require("cors"); const os = require("os"); const SequelizeStore = require("connect-session-sequelize")(session.Store); -const sequelizeInstance = require("./plugins/sql/instance"); +const sequelizeInstance = require("./models/sql/instance"); const passport = require("passport"); const { raccoonConfig } = require("./config-class"); From 3340f893fe8088eebe22764797602f0ada6f4d9c Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 13:16:02 +0800 Subject: [PATCH 003/365] build: add generate erd tool --- models/sql/generate-erd.js | 14 ++++++++++++++ package-lock.json | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 53 insertions(+) create mode 100644 models/sql/generate-erd.js diff --git a/models/sql/generate-erd.js b/models/sql/generate-erd.js new file mode 100644 index 00000000..f747929e --- /dev/null +++ b/models/sql/generate-erd.js @@ -0,0 +1,14 @@ +require("module-alias/register"); +const fsP = require("fs/promises"); +const sequelize = require("./instance"); +const sequelizeErd = require("sequelize-erd"); + + +require("./init").then(async()=> { + const svg = await sequelizeErd({ + source: sequelize + }); // sequelizeErd() returns a Promise + await fsP.writeFile("./erd.svg", svg); +}); + + diff --git a/package-lock.json b/package-lock.json index def24372..bc7a1725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "eslint-config-prettier": "^8.5.0", "mocha": "^10.2.0", "mongodb-memory-server": "^8.12.2", + "sequelize-erd": "^1.3.1", "standard-version": "^9.5.0", "swagger-jsdoc": "^6.2.8" } @@ -7513,6 +7514,25 @@ } } }, + "node_modules/sequelize-erd": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/sequelize-erd/-/sequelize-erd-1.3.1.tgz", + "integrity": "sha512-w5/gNkj0WTp80KMvMxrrTp3HyIn1B8F5XmTXP6PXQNoWi1HKazXe9IvlbmGVP2yxx/YTtX+QWatcwkDk8HLK9Q==", + "dev": true, + "dependencies": { + "commander": "^2.9.0", + "lodash": "^4.17.15" + }, + "bin": { + "sequelize-erd": "bin/generate" + } + }, + "node_modules/sequelize-erd/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/sequelize-pool": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", @@ -14727,6 +14747,24 @@ } } }, + "sequelize-erd": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/sequelize-erd/-/sequelize-erd-1.3.1.tgz", + "integrity": "sha512-w5/gNkj0WTp80KMvMxrrTp3HyIn1B8F5XmTXP6PXQNoWi1HKazXe9IvlbmGVP2yxx/YTtX+QWatcwkDk8HLK9Q==", + "dev": true, + "requires": { + "commander": "^2.9.0", + "lodash": "^4.17.15" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, "sequelize-pool": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", diff --git a/package.json b/package.json index acfe9916..c899dc01 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "eslint-config-prettier": "^8.5.0", "mocha": "^10.2.0", "mongodb-memory-server": "^8.12.2", + "sequelize-erd": "^1.3.1", "standard-version": "^9.5.0", "swagger-jsdoc": "^6.2.8" } From 99a564ef79af6b573641a6ad2c8c8d7efb5570e7 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 14:27:39 +0800 Subject: [PATCH 004/365] feat: update patientID (x00100020) to unique --- models/sql/models/patient.model.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index 1737f7bc..bbd16761 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -9,7 +9,9 @@ PatientModel.init({ type: DataTypes.INTEGER }, "x00100020": { - type: vrTypeMapping.LO + type: vrTypeMapping.LO, + allowNull: false, + unique: true }, "x00100021": { type: vrTypeMapping.LO From 6ffa6c1d06cb3b302b633f4f5dfe986ce1195ff8 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 14:29:46 +0800 Subject: [PATCH 005/365] feat: sql study model, store study --- .../STOW-RS/service/dicom-jpeg-generator.js | 3 + .../STOW-RS/service/stow-rs.service.js | 1 + models/sql/dicom-json-model.js | 24 +++++- models/sql/init.js | 6 ++ models/sql/models/study.model.js | 60 ++++++++++++++ models/sql/po/study.po.js | 82 +++++++++++++++++++ 6 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 models/sql/models/study.model.js create mode 100644 models/sql/po/study.po.js diff --git a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js index 2b5b4b3f..6e8b6ef0 100644 --- a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js @@ -21,6 +21,7 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { /** + * TODO * @private */ async insertStartTask_() { @@ -39,6 +40,7 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { } /** + * TODO * @private */ async insertEndTask_() { @@ -54,6 +56,7 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { } /** + * TODO * @private * @param {string} message */ diff --git a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js index 1ad91a31..43b57ffb 100644 --- a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -61,6 +61,7 @@ class SqlStowRsService extends StowRsService { this.responseCode = 409; } + // TODO // let dicomJsonBinaryDataModel = new DicomJsonBinaryDataModel(dicomJsonModel); // await dicomJsonBinaryDataModel.storeAllBinaryDataToFileAndDb(); // dicomJsonBinaryDataModel.replaceAllBinaryToURI(); diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index 5ad6e706..08b7907e 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -2,6 +2,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); const { PatientPersistentObject } = require("./po/patient.po"); +const { StudyPersistentObject } = require("./po/study.po"); class SqlDicomJsonModel extends DicomJsonModel { @@ -24,9 +25,10 @@ class SqlDicomJsonModel extends DicomJsonModel { delete dicomJsonClone.sopClass; delete dicomJsonClone.sopInstanceUID; - await Promise.all([ - this.storePatientCollection(dicomJsonClone) - ]); + let storedPatient = await this.storePatientCollection(dicomJsonClone); + let storedStudy = await this.storeStudyCollection(dicomJsonClone, storedPatient); + this.storeSeriesCollection(dicomJsonClone); + this.storeInstanceCollection(dicomJsonClone); } catch(e) { throw e; } @@ -35,6 +37,22 @@ class SqlDicomJsonModel extends DicomJsonModel { async storePatientCollection(dicomJson) { let patientPo = new PatientPersistentObject(dicomJson); let patient = await patientPo.createPatient(); + return patient; + } + + async storeStudyCollection(dicomJson, patient) { + // TODO + let studyPo = new StudyPersistentObject(dicomJson, patient); + let study = await studyPo.createStudy(); + return study; + } + + async storeSeriesCollection(dicomJson) { + // TODO + } + + async storeInstanceCollection(dicomJson) { + // TODO } } diff --git a/models/sql/init.js b/models/sql/init.js index b673d28b..e450d0e1 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -1,5 +1,6 @@ const { PersonNameModel } = require("./models/personName.model"); const { PatientModel } = require("./models/patient.model"); +const { StudyModel } = require("./models/study.model"); const sequelizeInstance = require("./instance"); async function init() { @@ -9,7 +10,12 @@ async function init() { PatientModel.belongsTo(PersonNameModel, { foreignKey: "x00100010" }); + StudyModel.belongsTo(PatientModel, { + foreignKey: "x00100020", + targetKey: "x00100020" + }); + //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({force: true}); } catch (e) { console.error('Unable to connect to the database:', e); diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js new file mode 100644 index 00000000..f46b0d8d --- /dev/null +++ b/models/sql/models/study.model.js @@ -0,0 +1,60 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class StudyModel extends Model { }; + +StudyModel.init({ + "x00100020": { + type: vrTypeMapping.LO, + allowNull: false + }, + "x00080005": { + type: DataTypes.JSON + }, + "x00080020": { + type: vrTypeMapping.DA + }, + "x00080030": { + type: vrTypeMapping.TM + }, + "x00080050": { + type: vrTypeMapping.SH + }, + "x00080056": { + type: vrTypeMapping.CS + }, + "x00080061": { + type: DataTypes.JSON + }, + "x00080090": { + type: vrTypeMapping.PN + }, + "x00080201": { + type: vrTypeMapping.SH + }, + "x0020000D": { + type: vrTypeMapping.UI, + allowNull: false, + unique: true + }, + "x00200010": { + type: vrTypeMapping.SH + }, + "x00201206": { + type: vrTypeMapping.IS + }, + "x00201208": { + type: vrTypeMapping.IS + }, + "json": { + type: DataTypes.JSON + } +}, { + sequelize: sequelizeInstance, + modelName: "Study", + tableName: "Study", + freezeTableName: true +}); + +module.exports.StudyModel = StudyModel; diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js new file mode 100644 index 00000000..d3e88f80 --- /dev/null +++ b/models/sql/po/study.po.js @@ -0,0 +1,82 @@ +const moment = require("moment"); +const _ = require("lodash"); +const { PersonNameModel } = require("../models/personName.model"); +const { PatientModel } = require("../models/patient.model"); +const { StudyModel } = require("../models/study.model"); +const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); + + + +class StudyPersistentObject { + constructor(dicomJson, patient) { + + this.json = {}; + Object.keys(tagsNeedStore.Study).forEach(key => { + let value = _.get(dicomJson, key); + value ? _.set(this.json, key, value) : undefined; + }); + this.patient = patient; + + this.x00080005 = _.get(dicomJson, "00080005.Value", undefined); + this.x00080020 = _.get(dicomJson, "00080020.Value.0", ""); + this.x00080030 = _.get(dicomJson, "00080030.Value.0", ""); + this.x00080050 = _.get(dicomJson, "00080050.Value.0", ""); + this.x00080056 = _.get(dicomJson, "00080056.Value.0", ""); + this.x00080061 = _.get(dicomJson, "00080061.Value.0", undefined); + this.x00080090 = _.get(dicomJson, "00080090.Value.0", ""); + this.x00080201 = _.get(dicomJson, "00080201.Value.0", ""); + this.x0020000D = _.get(dicomJson, "0020000D.Value.0", ""); + this.x00200010 = _.get(dicomJson, "00200010.Value.0", ""); + this.x00201206 = _.get(dicomJson, "00201206.Value.0", ""); + this.x00201208 = _.get(dicomJson, "00201208.Value.0", ""); + } + + async createReferringPhysicianName() { + if (this.x00080090) { + return await PersonNameModel.create({ + alphabetic: _.get(this.x00080090, "Alphabetic", undefined), + ideographic: _.get(this.x00080090, "Ideographic", undefined), + phonetic: _.get(this.x00080090, "Phonetic", undefined) + }); + } + return undefined; + } + + async createStudy() { + let [study, created] = await StudyModel.findOrCreate({ + where: { + x0020000D: this.x0020000D + }, + defaults: { + json: this.json, + x00100020: this.patient.x00100020, + x00080005 : this.x00080005, + x00080020 : this.x00080020, + x00080030 : this.x00080030 ? Number(this.x00080030) : undefined, + x00080050 : this.x00080050, + x00080056 : this.x00080056, + x00080061 : this.x00080061, + x00080201 : this.x00080201, + x0020000D : this.x0020000D, + x00200010 : this.x00200010, + x00201206 : this.x00201206, + x00201208 : this.x00201208 + } + }); + + if (created) { + let referringPhysicianName = await this.createReferringPhysicianName(); + study.x00080090 = referringPhysicianName ? referringPhysicianName.id : undefined; + await study.save(); + } + + return study; + } + + async updateModalitiesInStudy() { + //TODO + } + +} + +module.exports.StudyPersistentObject = StudyPersistentObject; \ No newline at end of file From c95f37952207ef950736ab36516e39b17d3a5b9f Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 14:31:43 +0800 Subject: [PATCH 006/365] chore: add sequelize initialized message --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index e1674a52..3599a08f 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,5 @@ // const mongodb = require("./models/mongodb/index"); -require("./models/sql/init"); +require("./models/sql/init").then(()=> console.log("Sequelize initialized")); const express = require("express"); const { createServer } = require("http"); const app = express(); From d16c095e0ee310f0750cbf777ed71a979821a6f3 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 15:43:18 +0800 Subject: [PATCH 007/365] feat: series sql model, and store series --- models/sql/dicom-json-model.js | 10 ++-- models/sql/models/series.model.js | 69 +++++++++++++++++++++++++++ models/sql/po/series.po.js | 79 +++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 models/sql/models/series.model.js create mode 100644 models/sql/po/series.po.js diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index 08b7907e..ca22593f 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -3,6 +3,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); const { PatientPersistentObject } = require("./po/patient.po"); const { StudyPersistentObject } = require("./po/study.po"); +const { SeriesPersistentObject } = require("./po/series.po"); class SqlDicomJsonModel extends DicomJsonModel { @@ -27,7 +28,7 @@ class SqlDicomJsonModel extends DicomJsonModel { let storedPatient = await this.storePatientCollection(dicomJsonClone); let storedStudy = await this.storeStudyCollection(dicomJsonClone, storedPatient); - this.storeSeriesCollection(dicomJsonClone); + await this.storeSeriesCollection(dicomJsonClone, storedStudy); this.storeInstanceCollection(dicomJsonClone); } catch(e) { throw e; @@ -41,14 +42,15 @@ class SqlDicomJsonModel extends DicomJsonModel { } async storeStudyCollection(dicomJson, patient) { - // TODO let studyPo = new StudyPersistentObject(dicomJson, patient); let study = await studyPo.createStudy(); return study; } - async storeSeriesCollection(dicomJson) { - // TODO + async storeSeriesCollection(dicomJson, study) { + let seriesPo = new SeriesPersistentObject(dicomJson, study); + let series = await seriesPo.createSeries(); + return series; } async storeInstanceCollection(dicomJson) { diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js new file mode 100644 index 00000000..146f4665 --- /dev/null +++ b/models/sql/models/series.model.js @@ -0,0 +1,69 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class SeriesModel extends Model { }; + +SeriesModel.init({ + "x0020000D": { + type: vrTypeMapping.UI, + allowNull: false + }, + "x0020000E": { + type: vrTypeMapping.UI, + allowNull: false, + unique: true + }, + "x00080021": { + type: vrTypeMapping.DA + }, + "x00080060": { + type: vrTypeMapping.CS + }, + "x0008103E": { + type: vrTypeMapping.LO + }, + "x0008103F": { + type: DataTypes.JSON + }, + "x00081050": { // 1-n + type: DataTypes.JSON + }, + "x00081052": { + type: DataTypes.JSON + }, + "x00081070": { // 1-n + type: DataTypes.JSON + }, + "x00081072": { + type: DataTypes.JSON + }, + "x00081250": { + type: DataTypes.JSON + }, + "x00200011": { + type: vrTypeMapping.IS + }, + "x00400244": { + type: vrTypeMapping.DA + }, + "x00400245": { + type: vrTypeMapping.TM + }, + "x00400275": { + type: DataTypes.JSON + }, + "x00080031": { + type: vrTypeMapping.TM + }, + "json": { + type: DataTypes.JSON + } +}, { + sequelize: sequelizeInstance, + modelName: "Series", + tableName: "Series", + freezeTableName: true +}); + +module.exports.SeriesModel = SeriesModel; diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js new file mode 100644 index 00000000..ae722bbc --- /dev/null +++ b/models/sql/po/series.po.js @@ -0,0 +1,79 @@ +const moment = require("moment"); +const _ = require("lodash"); +const { PersonNameModel } = require("../models/personName.model"); +const { SeriesModel } = require("../models/series.model"); + +const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); + +class SeriesPersistentObject { + constructor(dicomJson, study) { + + this.json = {}; + Object.keys(tagsNeedStore.Study).forEach(key => { + let value = _.get(dicomJson, key); + value ? _.set(this.json, key, value) : undefined; + }); + this.study = study; + + this.x0020000D = this.study.x0020000D; + this.x0020000E = _.get(dicomJson, "0020000E.Value.0", ""); + this.x00080021 = _.get(dicomJson, "00080021.Value.0", undefined); + this.x00080060 = _.get(dicomJson, "00080060.Value.0", ""); + this.x0008103E = _.get(dicomJson, "0008103E.Value.0", ""); + this.x0008103F = _.get(dicomJson, "0008103F.Value", undefined); + this.x00081050 = _.get(dicomJson, "00081050.Value", ""); + this.x00081052 = _.get(dicomJson, "00081052.Value", ""); + this.x00081070 = _.get(dicomJson, "00081070.Value", ""); + this.x00081072 = _.get(dicomJson, "00081072.Value", ""); + this.x00081250 = _.get(dicomJson, "00081250.Value", ""); + this.x00200011 = _.get(dicomJson, "00200011.Value.0", ""); + this.x00400244 = _.get(dicomJson, "00400244.Value.0", undefined); + this.x00400245 = _.get(dicomJson, "00400245.Value.0", ""); + this.x00400275 = _.get(dicomJson, "00400275.Value", ""); + this.x00080031 = _.get(dicomJson, "00080031.Value.0", ""); + } + + async createReferringPhysicianName() { + if (this.x00080090) { + return await PersonNameModel.create({ + alphabetic: _.get(this.x00080090, "Alphabetic", undefined), + ideographic: _.get(this.x00080090, "Ideographic", undefined), + phonetic: _.get(this.x00080090, "Phonetic", undefined) + }); + } + return undefined; + } + + async createSeries() { + let [series, created] = await SeriesModel.findOrCreate({ + where: { + x0020000D: this.x0020000D, + x0020000E: this.x0020000E + }, + defaults: { + json: this.json, + x0020000D: this.x0020000D, + x0020000E: this.x0020000E, + x00080021: this.x00080021, + x00080060: this.x00080060, + x0008103E: this.x0008103E, + x0008103F: this.x0008103F, + x00081050: this.x00081050, + x00081052: this.x00081052, + x00081070: this.x00081070, + x00081072: this.x00081072, + x00081250: this.x00081250, + x00200011: this.x00200011, + x00400244: this.x00400244, + x00400245: this.x00400245 ? Number(this.x00400245) : undefined, + x00400275: this.x00400275, + x00080031: this.x00080031 ? Number(this.x00080031) : undefined + } + }); + + return series; + } + +} + +module.exports.SeriesPersistentObject = SeriesPersistentObject; \ No newline at end of file From 19dce9ec0427a58be858a5359b73c6e7f0042873 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 17:12:27 +0800 Subject: [PATCH 008/365] feat: instance model, and store instance --- models/sql/dicom-json-model.js | 10 +-- models/sql/init.js | 11 ++++ models/sql/models/instance.mode.js | 58 +++++++++++++++++ models/sql/po/instance.po.js | 100 +++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 models/sql/models/instance.mode.js create mode 100644 models/sql/po/instance.po.js diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index ca22593f..2caa31c2 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -4,6 +4,7 @@ const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); const { PatientPersistentObject } = require("./po/patient.po"); const { StudyPersistentObject } = require("./po/study.po"); const { SeriesPersistentObject } = require("./po/series.po"); +const { InstancePersistentObject } = require("./po/instance.po"); class SqlDicomJsonModel extends DicomJsonModel { @@ -28,8 +29,8 @@ class SqlDicomJsonModel extends DicomJsonModel { let storedPatient = await this.storePatientCollection(dicomJsonClone); let storedStudy = await this.storeStudyCollection(dicomJsonClone, storedPatient); - await this.storeSeriesCollection(dicomJsonClone, storedStudy); - this.storeInstanceCollection(dicomJsonClone); + let storedSeries = await this.storeSeriesCollection(dicomJsonClone, storedStudy); + await this.storeInstanceCollection(dicomJsonClone, storedSeries); } catch(e) { throw e; } @@ -53,8 +54,9 @@ class SqlDicomJsonModel extends DicomJsonModel { return series; } - async storeInstanceCollection(dicomJson) { - // TODO + async storeInstanceCollection(dicomJson, series) { + let instancePo = new InstancePersistentObject(dicomJson, series); + return await instancePo.createInstance(); } } diff --git a/models/sql/init.js b/models/sql/init.js index e450d0e1..6317e33a 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -1,6 +1,9 @@ const { PersonNameModel } = require("./models/personName.model"); const { PatientModel } = require("./models/patient.model"); const { StudyModel } = require("./models/study.model"); +const { SeriesModel } = require("./models/series.model"); +const { InstanceModel } = require("./models/instance.mode"); + const sequelizeInstance = require("./instance"); async function init() { @@ -14,6 +17,14 @@ async function init() { foreignKey: "x00100020", targetKey: "x00100020" }); + SeriesModel.belongsTo(StudyModel, { + foreignKey: "x0020000D", + targetKey: "x0020000D" + }); + InstanceModel.belongsTo(SeriesModel, { + foreignKey: "x0020000E", + targetKey: "x0020000E" + }); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({force: true}); diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js new file mode 100644 index 00000000..1bd18690 --- /dev/null +++ b/models/sql/models/instance.mode.js @@ -0,0 +1,58 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class InstanceModel extends Model { }; + +InstanceModel.init({ + "x0020000D": { + type: vrTypeMapping.UI, + allowNull: false + }, + "x0020000E": { + type: vrTypeMapping.UI, + allowNull: false + }, + "x00080018": { + type: vrTypeMapping.UI, + allowNull: false, + unique: true + }, + "x00080016": { + type: vrTypeMapping.UI + }, + "x00080023": { + type: vrTypeMapping.DA + }, + "x00080033": { + type: vrTypeMapping.TM + }, + "x00200013": { + type: vrTypeMapping.IS + }, + "x0040A043": { //SQ + type: DataTypes.JSON + }, + "x0040A073": { + type: DataTypes.JSON + }, + "x0040A491": { + type: vrTypeMapping.CS + }, + "x0040A493": { + type: vrTypeMapping.CS + }, + "x0040A730": { + type: DataTypes.JSON + }, + "json": { + type: DataTypes.JSON + } +}, { + sequelize: sequelizeInstance, + modelName: "Instance", + tableName: "Instance", + freezeTableName: true +}); + +module.exports.InstanceModel = InstanceModel; diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js new file mode 100644 index 00000000..3fc6b41a --- /dev/null +++ b/models/sql/po/instance.po.js @@ -0,0 +1,100 @@ +const moment = require("moment"); +const _ = require("lodash"); +const { PersonNameModel } = require("../models/personName.model"); +const { InstanceModel } = require("../models/instance.mode"); + +const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); + +const INSTANCE_STORE_TAGS = { + "00080016": true, + "00080018": true, + "00080023": true, + "00080033": true, + "00200013": true, + "0040A043": true, + "0040A073": true, + "0040A491": true, + "0040A493": true, + "0040A730": true, + "00080008": true, + "0040A032": true, + "00081115": true, + "00280008": true, + "00280010": true, + "00280011": true, + "00280100": true, + "0040A370": true, + "0040A375": true, + "0040A504": true, + "0040A525": true, + "00420010": true, + "00420012": true, + "00700080": true, + "00700081": true, + "00700082": true, + "00700083": true, + "00700084": true, + "00080005": true, + "00081190": true, + "00080054": true, + "00080056": true, + ...tagsNeedStore.Patient, + ...tagsNeedStore.Study, + ...tagsNeedStore.Series +}; + +class InstancePersistentObject { + constructor(dicomJson, series) { + this.json = {}; + Object.keys(INSTANCE_STORE_TAGS).forEach(key => { + let value = _.get(dicomJson, key); + value ? _.set(this.json, key, value) : undefined; + }); + this.series = series; + + this.x0020000D = this.series.x0020000D; + this.x0020000E = this.series.x0020000E; + this.x00080018 = _.get(dicomJson, "00080018.Value.0", undefined); + this.x00080016 = _.get(dicomJson, "00080016.Value.0", undefined); + this.x00080023 = _.get(dicomJson, "00080023.Value.0", undefined); + this.x00080033 = _.get(dicomJson, "00080033.Value.0", undefined); + this.x00200013 = _.get(dicomJson, "00200013.Value.0", undefined); + this.x0040A043 = _.get(dicomJson, "0040A043.Value", undefined); + this.x0040A073 = _.get(dicomJson, "0040A073.Value", undefined); + this.x0040A491 = _.get(dicomJson, "0040A491.Value.0", undefined); + this.x0040A493 = _.get(dicomJson, "0040A493.Value.0", undefined); + this.x0040A730 = _.get(dicomJson, "0040A730.Value", undefined); + } + + + async createInstance() { + let [instance, created] = await InstanceModel.findOrCreate({ + where: { + x0020000D: this.x0020000D, + x0020000E: this.x0020000E, + x00080018: this.x00080018 + }, + defaults: { + json: this.json, + x0020000D: this.x0020000D, + x0020000E: this.x0020000E, + x00080018: this.x00080018, + x00080016: this.x00080016, + x00080023: this.x00080023, + x00080033: this.x00080033 ? Number(this.x00080033) : undefined, + x00200013: this.x00200013, + x0040A043: this.x0040A043, + x0040A073: this.x0040A073, + x0040A491: this.x0040A491, + x0040A493: this.x0040A493, + x0040A730: this.x0040A730 + } + }); + + + return instance; + } + +} + +module.exports.InstancePersistentObject = InstancePersistentObject; \ No newline at end of file From e07cd58cab3137e5db8922b2ad77023340a526b9 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 17:22:17 +0800 Subject: [PATCH 009/365] feat: add store dest in models of each level --- models/sql/models/instance.mode.js | 3 +++ models/sql/models/series.model.js | 3 +++ models/sql/models/study.model.js | 3 +++ models/sql/po/instance.po.js | 4 +++- models/sql/po/series.po.js | 4 +++- models/sql/po/study.po.js | 4 +++- 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js index 1bd18690..2d06b99c 100644 --- a/models/sql/models/instance.mode.js +++ b/models/sql/models/instance.mode.js @@ -5,6 +5,9 @@ const { vrTypeMapping } = require("../vrTypeMapping"); class InstanceModel extends Model { }; InstanceModel.init({ + "instancePath": { + type: DataTypes.TEXT("long") + }, "x0020000D": { type: vrTypeMapping.UI, allowNull: false diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 146f4665..86194e04 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -5,6 +5,9 @@ const { vrTypeMapping } = require("../vrTypeMapping"); class SeriesModel extends Model { }; SeriesModel.init({ + "seriesPath": { + type: DataTypes.TEXT("long") + }, "x0020000D": { type: vrTypeMapping.UI, allowNull: false diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index f46b0d8d..e616d388 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -5,6 +5,9 @@ const { vrTypeMapping } = require("../vrTypeMapping"); class StudyModel extends Model { }; StudyModel.init({ + "studyPath": { + type: DataTypes.TEXT("long") + }, "x00100020": { type: vrTypeMapping.LO, allowNull: false diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 3fc6b41a..4ff5c925 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -51,6 +51,7 @@ class InstancePersistentObject { value ? _.set(this.json, key, value) : undefined; }); this.series = series; + this.instancePath = _.get(dicomJson, "instancePath", ""); this.x0020000D = this.series.x0020000D; this.x0020000E = this.series.x0020000E; @@ -87,7 +88,8 @@ class InstancePersistentObject { x0040A073: this.x0040A073, x0040A491: this.x0040A491, x0040A493: this.x0040A493, - x0040A730: this.x0040A730 + x0040A730: this.x0040A730, + instancePath: this.instancePath } }); diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index ae722bbc..b39d8e5e 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -14,6 +14,7 @@ class SeriesPersistentObject { value ? _.set(this.json, key, value) : undefined; }); this.study = study; + this.seriesPath = _.get(dicomJson, "seriesPath", ""); this.x0020000D = this.study.x0020000D; this.x0020000E = _.get(dicomJson, "0020000E.Value.0", ""); @@ -67,7 +68,8 @@ class SeriesPersistentObject { x00400244: this.x00400244, x00400245: this.x00400245 ? Number(this.x00400245) : undefined, x00400275: this.x00400275, - x00080031: this.x00080031 ? Number(this.x00080031) : undefined + x00080031: this.x00080031 ? Number(this.x00080031) : undefined, + seriesPath: this.seriesPath } }); diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js index d3e88f80..ad644948 100644 --- a/models/sql/po/study.po.js +++ b/models/sql/po/study.po.js @@ -16,6 +16,7 @@ class StudyPersistentObject { value ? _.set(this.json, key, value) : undefined; }); this.patient = patient; + this.studyPath = _.get(dicomJson, "studyPath", ""); this.x00080005 = _.get(dicomJson, "00080005.Value", undefined); this.x00080020 = _.get(dicomJson, "00080020.Value.0", ""); @@ -60,7 +61,8 @@ class StudyPersistentObject { x0020000D : this.x0020000D, x00200010 : this.x00200010, x00201206 : this.x00201206, - x00201208 : this.x00201208 + x00201208 : this.x00201208, + studyPath: this.studyPath } }); From e6a3c392845130a6a5c9e24733fb4c7d6cff3b76 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 18:05:10 +0800 Subject: [PATCH 010/365] feat: update modalities in study after storing --- models/sql/dicom-json-model.js | 3 +++ models/sql/models/study.model.js | 32 ++++++++++++++++++++++++++++++-- models/sql/po/study.po.js | 5 ----- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index 2caa31c2..05fa7dbc 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -5,6 +5,7 @@ const { PatientPersistentObject } = require("./po/patient.po"); const { StudyPersistentObject } = require("./po/study.po"); const { SeriesPersistentObject } = require("./po/series.po"); const { InstancePersistentObject } = require("./po/instance.po"); +const { StudyModel } = require("./models/study.model"); class SqlDicomJsonModel extends DicomJsonModel { @@ -31,6 +32,8 @@ class SqlDicomJsonModel extends DicomJsonModel { let storedStudy = await this.storeStudyCollection(dicomJsonClone, storedPatient); let storedSeries = await this.storeSeriesCollection(dicomJsonClone, storedStudy); await this.storeInstanceCollection(dicomJsonClone, storedSeries); + + await StudyModel.updateModalitiesInStudy(storedStudy.x0020000D); } catch(e) { throw e; } diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index e616d388..fd224808 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -1,6 +1,8 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); +const { SeriesModel } = require("./series.model"); +const _ = require("lodash"); class StudyModel extends Model { }; @@ -27,8 +29,8 @@ StudyModel.init({ "x00080056": { type: vrTypeMapping.CS }, - "x00080061": { - type: DataTypes.JSON + "x00080061": { + type: DataTypes.JSON }, "x00080090": { type: vrTypeMapping.PN @@ -60,4 +62,30 @@ StudyModel.init({ freezeTableName: true }); +StudyModel.updateModalitiesInStudy = async function (studyInstanceUid) { + let seriesArray = await SeriesModel.findAll({ + where: { + x0020000D: studyInstanceUid + }, + attributes: [ + [Sequelize.fn("DISTINCT", Sequelize.col("x00080060")), "modality"] + ] + }); + + let modalities = []; + for(let item of seriesArray) { + if (_.get(item, "dataValues.modality")) + modalities.push(item.dataValues.modality); + } + + await StudyModel.update({ + x00080061: modalities + }, { + where: { + x0020000D: studyInstanceUid + + } + }); +}; + module.exports.StudyModel = StudyModel; diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js index ad644948..8deb7d3b 100644 --- a/models/sql/po/study.po.js +++ b/models/sql/po/study.po.js @@ -1,7 +1,6 @@ const moment = require("moment"); const _ = require("lodash"); const { PersonNameModel } = require("../models/personName.model"); -const { PatientModel } = require("../models/patient.model"); const { StudyModel } = require("../models/study.model"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); @@ -75,10 +74,6 @@ class StudyPersistentObject { return study; } - async updateModalitiesInStudy() { - //TODO - } - } module.exports.StudyPersistentObject = StudyPersistentObject; \ No newline at end of file From ec23381d276c0cc9efa4b51e935e61087adeee06 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 18:23:35 +0800 Subject: [PATCH 011/365] feat: sql dicom bulk data --- .../STOW-RS/service/stow-rs.service.js | 9 +- models/sql/dicom-json-model.js | 87 ++++++++++++++++++- models/sql/init.js | 1 + models/sql/models/dicomBulkData.model.js | 30 +++++++ 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 models/sql/models/dicomBulkData.model.js diff --git a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js index 43b57ffb..8c47f54b 100644 --- a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -3,7 +3,7 @@ const _ = require("lodash"); const { DicomJsonParser } = require("@models/DICOM/dicom-json-parser"); const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); const { DicomFileSaver } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-file-saver"); -const { SqlDicomJsonModel: DicomJsonModel } = require("@models/sql/dicom-json-model"); +const { SqlDicomJsonModel: DicomJsonModel, SqlDicomJsonBinaryDataModel: DicomJsonBinaryDataModel } = require("@models/sql/dicom-json-model"); const { SqlDicomJpegGenerator: DicomJpegGenerator } = require("./dicom-jpeg-generator"); class SqlStowRsService extends StowRsService { @@ -61,10 +61,9 @@ class SqlStowRsService extends StowRsService { this.responseCode = 409; } - // TODO - // let dicomJsonBinaryDataModel = new DicomJsonBinaryDataModel(dicomJsonModel); - // await dicomJsonBinaryDataModel.storeAllBinaryDataToFileAndDb(); - // dicomJsonBinaryDataModel.replaceAllBinaryToURI(); + let dicomJsonBinaryDataModel = new DicomJsonBinaryDataModel(dicomJsonModel); + await dicomJsonBinaryDataModel.storeAllBinaryDataToFileAndDb(); + dicomJsonBinaryDataModel.replaceAllBinaryToURI(); let dicomFileSaver = new DicomFileSaver(file, dicomJsonModel); let dicomFileSaveInfo = await dicomFileSaver.saveAndGetSaveInfo(); diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index 05fa7dbc..56f478bf 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -1,11 +1,19 @@ const _ = require("lodash"); +const shortHash = require("shorthash2"); +const fsP = require("fs/promises"); +const path = require("path"); +const mkdirp = require("mkdirp"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel, DicomJsonBinaryDataModel } = require("@models/DICOM/dicom-json-model"); const { PatientPersistentObject } = require("./po/patient.po"); const { StudyPersistentObject } = require("./po/study.po"); const { SeriesPersistentObject } = require("./po/series.po"); const { InstancePersistentObject } = require("./po/instance.po"); const { StudyModel } = require("./models/study.model"); +const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); + +const { raccoonConfig } = require("@root/config-class"); +const { logger } = require("@root/utils/logs/log"); class SqlDicomJsonModel extends DicomJsonModel { @@ -63,5 +71,80 @@ class SqlDicomJsonModel extends DicomJsonModel { } } +class SqlDicomJsonBinaryDataModel extends DicomJsonBinaryDataModel { + constructor(dicomJsonModel) { + super(dicomJsonModel); + } + + async storeAllBinaryDataToFileAndDb() { + let { + sopInstanceUID + } = this.dicomJsonModel.uidObj; + + let shortInstanceUID = shortHash(sopInstanceUID); + + + for(let i = 0; i < this.pathGroupOfBinaryProperties.length ; i++) { + let relativeFilename = `files/bulkData/${shortInstanceUID}/`; + let pathOfBinaryProperty = this.pathGroupOfBinaryProperties[i]; + + let binaryData = _.get(this.dicomJsonModel.dicomJson, pathOfBinaryProperty); + + if(binaryData) { + relativeFilename += `${pathOfBinaryProperty}.raw`; + let filename = path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + relativeFilename + ); + + mkdirp.sync( + path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + `files/bulkData/${shortInstanceUID}` + ) + ); + + await fsP.writeFile(filename, Buffer.from(binaryData, "base64")); + logger.info(`[STOW-RS] [Store binary data to ${filename}]`); + + let bulkData = new BulkData(this.dicomJsonModel.uidObj, relativeFilename, pathOfBinaryProperty); + await bulkData.storeToDb(); + } + + } + } + +} + +class BulkData { + constructor(uidObj, filename, pathOfBinaryProperty) { + /** @type {import("../../utils/typeDef/dicom").UIDObject} */ + this.uidObj = uidObj; + this.filename = filename; + this.pathOfBinaryProperty = pathOfBinaryProperty; + } + + async storeToDb() { + + let item = { + studyUID: this.uidObj.studyUID, + seriesUID: this.uidObj.seriesUID, + instanceUID: this.uidObj.sopInstanceUID, + filename: this.filename, + binaryValuePath: this.pathOfBinaryProperty + }; + + await DicomBulkDataModel.findOrCreate({ + where: { + instanceUID: this.uidObj.sopInstanceUID, + binaryValuePath: this.pathOfBinaryProperty + }, + defaults: item + }); + + logger.info(`[STOW-RS] [Store bulkdata ${JSON.stringify(item)} successful]`); + } +} -module.exports.SqlDicomJsonModel = SqlDicomJsonModel; \ No newline at end of file +module.exports.SqlDicomJsonModel = SqlDicomJsonModel; +module.exports.SqlDicomJsonBinaryDataModel = SqlDicomJsonBinaryDataModel; \ No newline at end of file diff --git a/models/sql/init.js b/models/sql/init.js index 6317e33a..850bdc39 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -3,6 +3,7 @@ const { PatientModel } = require("./models/patient.model"); const { StudyModel } = require("./models/study.model"); const { SeriesModel } = require("./models/series.model"); const { InstanceModel } = require("./models/instance.mode"); +const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); const sequelizeInstance = require("./instance"); diff --git a/models/sql/models/dicomBulkData.model.js b/models/sql/models/dicomBulkData.model.js new file mode 100644 index 00000000..1ee82028 --- /dev/null +++ b/models/sql/models/dicomBulkData.model.js @@ -0,0 +1,30 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class DicomBulkDataModel extends Model {}; + +DicomBulkDataModel.init({ + studyUID: { + type: vrTypeMapping.UI + }, + seriesUID: { + type: vrTypeMapping.UI + }, + instanceUID: { + type: vrTypeMapping.UI + }, + filename: { + type: DataTypes.TEXT("long") + }, + binaryValuePath: { + type: DataTypes.TEXT("medium") + } +}, { + sequelize: sequelizeInstance, + modelName: "DicomBulkData", + tableName: "DicomBulkData", + freezeTableName: true +}); + +module.exports.DicomBulkDataModel = DicomBulkDataModel; From f28cd0b8fd81db4f64ac88e4ec6754d150ac0d7e Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 1 Aug 2023 20:39:51 +0800 Subject: [PATCH 012/365] feat(sql): dicom to jpeg task model --- .../STOW-RS/service/dicom-jpeg-generator.js | 12 ++-- .../STOW-RS/service/stow-rs.service.js | 4 +- models/sql/models/dicomToJpegTask.model.js | 61 +++++++++++++++++++ 3 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 models/sql/models/dicomToJpegTask.model.js diff --git a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js index 6e8b6ef0..c4a2a0bd 100644 --- a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js @@ -2,6 +2,7 @@ const fs = require("fs"); const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); const colorette = require("colorette"); const { DicomJpegGenerator } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator"); +const { DicomToJpegTaskModel } = require("@models/sql/models/dicomToJpegTask.model"); /** * @typedef JsDcm2JpegTask * @property {Dcm2JpgExecutor$Dcm2JpgOptions} jsDcm2Jpeg @@ -21,7 +22,6 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { /** - * TODO * @private */ async insertStartTask_() { @@ -35,8 +35,8 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { finishedTime: null, fileSize: `${(fs.statSync(this.dicomInstanceFilename).size / 1024 / 1024).toFixed(3)}MB` }; - - + + await DicomToJpegTaskModel.insertOrUpdate(startTaskObj); } /** @@ -52,7 +52,8 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { message: "generated", finishedTime: new Date() }; - + + await DicomToJpegTaskModel.insertOrUpdate(endTaskObj); } /** @@ -69,7 +70,8 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { message: message, finishedTime: new Date() }; - + + await DicomToJpegTaskModel.insertOrUpdate(errorTaskObj); } } diff --git a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js index 8c47f54b..4ad9d694 100644 --- a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -34,8 +34,8 @@ class SqlStowRsService extends StowRsService { // } //generate JPEG - // let dicomJpegGenerator = new DicomJpegGenerator(dicomJsonModel, dicomFileSaveInfo.instancePath); - // dicomJpegGenerator.generateAllFrames(); + let dicomJpegGenerator = new DicomJpegGenerator(dicomJsonModel, dicomFileSaveInfo.instancePath); + dicomJpegGenerator.generateAllFrames(); } return { diff --git a/models/sql/models/dicomToJpegTask.model.js b/models/sql/models/dicomToJpegTask.model.js new file mode 100644 index 00000000..57c3c03a --- /dev/null +++ b/models/sql/models/dicomToJpegTask.model.js @@ -0,0 +1,61 @@ +const _ = require("lodash"); +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class DicomToJpegTaskModel extends Model {}; + +DicomToJpegTaskModel.init({ + studyUID: { + type: vrTypeMapping.UI + }, + seriesUID: { + type: vrTypeMapping.UI + }, + instanceUID: { + type: vrTypeMapping.UI + }, + message: { + type: DataTypes.TEXT("long") + }, + status: { + type: DataTypes.BOOLEAN + }, + taskTime: { + type: DataTypes.DATE + }, + finishedTime: { + type: DataTypes.DATE + }, + fileSize: { + type: DataTypes.TEXT("medium") + } +}, { + sequelize: sequelizeInstance, + modelName: "DicomToJpegTask", + tableName: "DicomToJpegTask", + freezeTableName: true +}); + +DicomToJpegTaskModel.insertOrUpdate = async (item) => { + try { + let [task, created] = await DicomToJpegTaskModel.findOrCreate({ + where: { + studyUID: item.studyUID, + seriesUID: item.seriesUID, + instanceUID: item.instanceUID + }, + defaults: item + }); + + // update + if (!created) { + _.assign(task, item); + await task.save(); + } + } catch(e) { + throw e; + } +}; + +module.exports.DicomToJpegTaskModel = DicomToJpegTaskModel; From 8f148297598d781ac07efa313ff23d00b898149e Mon Sep 17 00:00:00 2001 From: chin Date: Wed, 2 Aug 2023 00:01:41 +0800 Subject: [PATCH 013/365] feat(WIP): query --- .../controller/QIDO-RS/queryAllStudies.js | 49 +++ .../QIDO-RS/service/QIDO-RS.service.js | 146 ++++++++ .../QIDO-RS/service/querybuilder.js | 170 ++++++++++ api-sql/dicom-web/qido-rs.route.js | 311 ++++++++++++++++++ models/sql/init.js | 2 +- routes.js | 2 +- 6 files changed, 678 insertions(+), 2 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js create mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js create mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js create mode 100644 api-sql/dicom-web/qido-rs.route.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js new file mode 100644 index 00000000..6fbd239a --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js @@ -0,0 +1,49 @@ +const { + SqlQidoRsService: QidoRsService +} = require("./service/QIDO-RS.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class QueryAllStudiesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "QIDO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info(`Query All Studies`); + + try { + + let qidoRsService = new QidoRsService(this.request, this.response, "study"); + + await qidoRsService.getAndResponseDicomJson(); + + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: errorStr + })); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new QueryAllStudiesController(req, res); + + await controller.doPipeline(); +}; + diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js new file mode 100644 index 00000000..fd88c3bb --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -0,0 +1,146 @@ +const _ = require("lodash"); + +const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { DicomWebService } = require("@root/api/dicom-web/service/dicom-web.service"); +const { StudyQueryBuilder } = require("./querybuilder"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { + DicomWebServiceError, + DicomWebStatusCodes +} = require("@error/dicom-web-service"); +const { StudyModel } = require("@models/sql/models/study.model"); + + +class SqlQidoRsService extends QidoRsService { + /** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {"study" | "series" | "instance"} level + */ + constructor(req, res, level = "instance") { + super(req, res, level); + } + + async getAndResponseDicomJson() { + try { + + let dicomWebService = new DicomWebService(this.request, this.response); + + let queryOptions = { + query: this.query, + skip: this.skip_, + limit: this.limit_, + includeFields: this.includeFields_, + retrieveBaseUrl: `${dicomWebService.getBasicURL()}/studies`, + requestParams: this.request.params + }; + + let qidoDicomJsonFactory = new QidoDicomJsonFactory(queryOptions, this.level); + + let dicomJson = await qidoDicomJsonFactory.getDicomJson(); + + let dicomJsonLength = _.get(dicomJson, "length", 0); + if (dicomJsonLength > 0) { + this.response.writeHead(200, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify(dicomJson)); + } else { + this.response.writeHead(204); + this.response.end(); + } + + } catch (e) { + throw e; + } + } + + /** + * @private + */ + initQuery_() { + let query = _.cloneDeep(this.request.query); + let queryKeys = Object.keys(query).sort(); + for (let i = 0; i < queryKeys.length; i++) { + let queryKey = queryKeys[i]; + if (!query[queryKey]) delete query[queryKey]; + } + + this.query = convertAllQueryToDicomTag(query); + } +} + +class QidoDicomJsonFactory { + + /** + * + * @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {string} level + */ + constructor(queryOptions, level = "instance") { + this.level = level; + + this.getDicomJsonByLevel = { + "patient": async () => { + // return await getPatientDicomJson(queryOptions); + }, + "study": async () => { + // return await getStudyDicomJson(queryOptions); + let queryBuilder = new StudyQueryBuilder(queryOptions); + queryBuilder.build(); + let studies = await StudyModel.findAll({ + where: queryBuilder.query + }); + console.log(studies); + }, + "series": async () => { + // return await getSeriesDicomJson(queryOptions); + }, + "instance": async () => { + // return await getInstanceDicomJson(queryOptions); + } + }; + } + + async getDicomJson() { + return await this.getDicomJsonByLevel[this.level](); + } +} + + +/** + * Convert All of name(tags, keyword) of queries to tags number + * @param {Object} iParam The request query. + * @returns + */ +function convertAllQueryToDicomTag(iParam) { + let keys = Object.keys(iParam); + let newQS = {}; + for (let i = 0; i < keys.length; i++) { + let keyName = keys[i]; + let keyNameSplit = keyName.split("."); + let newKeyNames = []; + for (let x = 0; x < keyNameSplit.length; x++) { + if (dictionary.keyword[keyNameSplit[x]]) { + newKeyNames.push(dictionary.keyword[keyNameSplit[x]]); + } else if (dictionary.tag[keyNameSplit[x]]) { + newKeyNames.push(keyNameSplit); + } + } + if (newKeyNames.length === 0) { + throw new DicomWebServiceError( + DicomWebStatusCodes.InvalidArgumentValue, + `Invalid request query: ${keyNameSplit}`, + 400 + ); + } else if (newKeyNames.length >= 2) { + newKeyNames.push("Value"); + } + let retKeyName = newKeyNames.join("."); + newQS[retKeyName] = iParam[keyName]; + } + return newQS; +} + +module.exports.SqlQidoRsService = SqlQidoRsService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js new file mode 100644 index 00000000..3c13fac9 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -0,0 +1,170 @@ +const _ = require("lodash"); +const moment = require("moment"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { Op } = require("sequelize"); + +class BaseQueryBuilder { + constructor(queryOptions) { + this.queryOptions = queryOptions; + this.personQuery = []; + } + + comma(key, value) { + let $or = []; + let valueCommaSplit = value.split(","); + for (let i = 0; i < valueCommaSplit.length; i++) { + let obj = {}; + obj[key] = valueCommaSplit[i]; + $or.push(obj); + } + return $or; + } + + getStringQuery(tag, value) { + return { + [`x${tag}`]: value + }; + } + + getStringArrayQuery(tag, value) { + //TODO + } + + getNumberQuery(tag, value) { + return { + [`x${tag}`]: Number(value) + }; + } + + getNumberArrayQuery(tag, value) { + //TODO + } + + getPersonNameQuery(value) { + return { + [Op.or]: { + alphabetic: value, + ideographic: value, + phonetic: value + } + }; + } + + /** + * + * @param {string} tag + * @param {string} value + */ + getDateQuery(tag, value) { + let dashIndex = value.indexOf("-"); + if (dashIndex === 0) { // -YYYYMMDD + return { + [`x${tag}`]: { + [Op.lte]: this.dateStringToSqlDateOnly(value) + } + }; + } else if (dashIndex === value.length - 1) { // YYYYMMDD- + return { + [`x${tag}`]: { + [Op.gte]: this.dateStringToSqlDateOnly(value) + } + }; + } else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD + return { + [`x${tag}`]: { + [Op.and]: [ + { [Op.gte]: this.dateStringToSqlDateOnly(value.substring(0, dashIndex)) }, + { [Op.lte]: this.dateStringToSqlDateOnly(value.substring(dashIndex + 1)) } + ] + } + }; + } else { // YYYYMMDD + return { + [`x${tag}`]: this.dateStringToSqlDateOnly(value) + }; + } + } + + dateStringToSqlDateOnly(value) { + return moment(value, "YYYYMMDD").format("YYYY-MM-DD"); + } + + + /** + * + * @param {string} value + * @returns + */ + getWildCardQuery(value) { + let wildCardIndex = value.indexOf("*"); + let questionIndex = value.indexOf("?"); + + if (wildCardIndex >= 0 || questionIndex >= 0) { + value = value.replace(/\*/gm, "%"); + value = value.replace(/\?/gm, "_"); + } + + return value; + } + + /** + * + * @param {*} q + * @see {@link https://stackoverflow.com/questions/60598225/how-to-merge-javascript-object-containing-symbols "How to merge javascript object containing symbols?"} + */ + mergeQuery(q) { + _.mergeWith(this.query, q, (a ,b) => { + if (!_.isObject(b)) return b; + + return Array.isArray(a) ? [...a, ...b] : { ...a, ...b }; + }); + } +} + +class StudyQueryBuilder extends BaseQueryBuilder { + constructor(queryOptions) { + super(queryOptions); + this.query = {}; + } + + build() { + for (let key in this.queryOptions.query) { + let commaValue = this.comma(key, this.queryOptions.query[key]); + + for (let i = 0; i < commaValue.length; i++) { + let value = this.getWildCardQuery(commaValue[i][key]); + try { + this[`get${dictionary.tag[key]}`](value); + } catch (e) { + if (e.message.includes("not a function")) break; + } + } + } + console.log(this.query); + } + + getStudyInstanceUID(value) { + let q = this.getStringQuery(dictionary.keyword.StudyInstanceUID, value); + _.merge(this.query, q); + } + + getPatientName(value) { + let q = this.getPersonNameQuery(value); + this.personQuery.push(q); + } + + getPatientID(value) { + let q = this.getStringQuery(dictionary.keyword.PatientID, value); + _.merge(this.query, q); + } + + getStudyDate(value) { + let q = this.getDateQuery(dictionary.keyword.StudyDate, value); + this.mergeQuery(q); + } + +} + + +module.exports.BaseQueryBuilder = BaseQueryBuilder; +module.exports.StudyQueryBuilder = StudyQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js new file mode 100644 index 00000000..ea74e98c --- /dev/null +++ b/api-sql/dicom-web/qido-rs.route.js @@ -0,0 +1,311 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi, stringArrayJoi } = require("@root/api/validator"); +const router = express(); +const { + dictionary +} = require("../../models/DICOM/dicom-tags-dic"); + +const KEYWORD_KEYS = Object.keys(dictionary.keyword); +const HEX_KEYS = Object.keys(dictionary.tag); + +//#region QIDO-RS +const queryValidation = { + limit: Joi.number().integer().min(1).max(100), + offset: Joi.number().integer().min(0), + includefield: stringArrayJoi.stringArray().items( + Joi.string().custom( + (attribute, helper) => { + if (!isValidAttribute(attribute)) { + return helper.message(`Invalid DICOM attribute: ${attribute}, please enter valid keyword or tag`); + } + return convertKeywordToHex(attribute); + } + ) + ).single() +}; + +/** + * + * @param {string} attribute + */ +function isValidAttribute(attribute) { + if (KEYWORD_KEYS.indexOf(attribute) >= 0 || + HEX_KEYS.indexOf(attribute) >= 0 || + attribute === "all") { + + return true; + } + + return false; +} + +function convertKeywordToHex(attribute) { + if (KEYWORD_KEYS.indexOf(attribute) >= 0) { + return dictionary.keyword[attribute]; + } + return attribute; +} + + +/** + * @openapi + * /dicom-web/studies: + * get: + * tags: + * - QIDO-RS + * description: Query for studies + * parameters: + * - $ref: "#/components/parameters/StudyDate" + * - $ref: "#/components/parameters/StudyTime" + * - $ref: "#/components/parameters/AccessionNumber" + * - $ref: "#/components/parameters/ModalitiesInStudy" + * - $ref: "#/components/parameters/ReferringPhysicianName" + * - $ref: "#/components/parameters/PatientName" + * - $ref: "#/components/parameters/PatientID" + * - $ref: "#/components/parameters/StudyID" + * responses: + * 200: + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + * items: + * allOf: + * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" + */ +router.get("/studies", validateParams(queryValidation, "query", { + allowUnknown: true +}), require("./controller/QIDO-RS/queryAllStudies")); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series: + * get: + * tags: + * - QIDO-RS + * description: Query for series from specific study's UID + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/StudyDate" + * - $ref: "#/components/parameters/StudyTime" + * - $ref: "#/components/parameters/AccessionNumber" + * - $ref: "#/components/parameters/ModalitiesInStudy" + * - $ref: "#/components/parameters/ReferringPhysicianName" + * - $ref: "#/components/parameters/PatientName" + * - $ref: "#/components/parameters/PatientID" + * - $ref: "#/components/parameters/StudyID" + * - $ref: "#/components/parameters/Modality" + * - $ref: "#/components/parameters/SeriesNumber" + * responses: + * 200: + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + * items: + * allOf: + * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" + * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" + */ +// router.get( +// "/studies/:studyUID/series", validateParams(queryValidation, "query", { +// allowUnknown: true +// }), +// require("./controller/QIDO-RS/queryStudies-Series") +// ); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/instances: + * get: + * tags: + * - QIDO-RS + * description: Query for studies + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/StudyDate" + * - $ref: "#/components/parameters/StudyTime" + * - $ref: "#/components/parameters/AccessionNumber" + * - $ref: "#/components/parameters/ModalitiesInStudy" + * - $ref: "#/components/parameters/ReferringPhysicianName" + * - $ref: "#/components/parameters/PatientName" + * - $ref: "#/components/parameters/PatientID" + * - $ref: "#/components/parameters/StudyID" + * - $ref: "#/components/parameters/Modality" + * - $ref: "#/components/parameters/SeriesNumber" + * - $ref: "#/components/parameters/SOPClassUID" + * - $ref: "#/components/parameters/InstanceNumber" + * responses: + * 200: + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + * items: + * allOf: + * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" + * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" + * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" + */ +// router.get( +// "/studies/:studyUID/instances", validateParams(queryValidation, "query", { +// allowUnknown: true +// }), +// require("./controller/QIDO-RS/queryStudies-Instances") +// ); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances: + * get: + * tags: + * - QIDO-RS + * description: Query for studies + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/StudyDate" + * - $ref: "#/components/parameters/StudyTime" + * - $ref: "#/components/parameters/AccessionNumber" + * - $ref: "#/components/parameters/ModalitiesInStudy" + * - $ref: "#/components/parameters/ReferringPhysicianName" + * - $ref: "#/components/parameters/PatientName" + * - $ref: "#/components/parameters/PatientID" + * - $ref: "#/components/parameters/StudyID" + * - $ref: "#/components/parameters/Modality" + * - $ref: "#/components/parameters/SeriesNumber" + * - $ref: "#/components/parameters/SOPClassUID" + * - $ref: "#/components/parameters/InstanceNumber" + * responses: + * 200: + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + * items: + * allOf: + * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" + * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" + * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" + */ +// router.get( +// "/studies/:studyUID/series/:seriesUID/instances", validateParams(queryValidation, "query", { +// allowUnknown: true +// }), +// require("./controller/QIDO-RS/queryStudies-Series-Instance") +// ); + +/** + * @openapi + * /dicom-web/series: + * get: + * tags: + * - QIDO-RS + * description: Query all series in server + * parameters: + * - $ref: "#/components/parameters/StudyDate" + * - $ref: "#/components/parameters/StudyTime" + * - $ref: "#/components/parameters/AccessionNumber" + * - $ref: "#/components/parameters/ModalitiesInStudy" + * - $ref: "#/components/parameters/ReferringPhysicianName" + * - $ref: "#/components/parameters/PatientName" + * - $ref: "#/components/parameters/PatientID" + * - $ref: "#/components/parameters/StudyID" + * - $ref: "#/components/parameters/Modality" + * - $ref: "#/components/parameters/SeriesNumber" + * responses: + * 200: + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + * items: + * allOf: + * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" + * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" + * + */ +// router.get( +// "/series", validateParams(queryValidation, "query", { +// allowUnknown: true +// }), +// require("./controller/QIDO-RS/queryAllSeries") +// ); + +/** + * @openapi + * /dicom-web/instances: + * get: + * tags: + * - QIDO-RS + * description: Query all instances in server + * parameters: + * - $ref: "#/components/parameters/StudyDate" + * - $ref: "#/components/parameters/StudyTime" + * - $ref: "#/components/parameters/AccessionNumber" + * - $ref: "#/components/parameters/ModalitiesInStudy" + * - $ref: "#/components/parameters/ReferringPhysicianName" + * - $ref: "#/components/parameters/PatientName" + * - $ref: "#/components/parameters/PatientID" + * - $ref: "#/components/parameters/StudyID" + * - $ref: "#/components/parameters/Modality" + * - $ref: "#/components/parameters/SeriesNumber" + * - $ref: "#/components/parameters/SOPClassUID" + * - $ref: "#/components/parameters/InstanceNumber" + * responses: + * 200: + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + * items: + * allOf: + * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" + * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" + * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" + */ +// router.get( +// "/instances", validateParams(queryValidation, "query", { +// allowUnknown: true +// }), +// require("./controller/QIDO-RS/queryAllInstances") +// ); + +/** + * @openapi + * /dicom-web/patients: + * get: + * tags: + * - QIDO-RS + * description: Query all patients in server + * parameters: + * - $ref: "#/components/parameters/PatientName" + * - $ref: "#/components/parameters/PatientID" + * - $ref: "#/components/parameters/PatientBirthDate" + * - $ref: "#/components/parameters/PatientBirthTime" + * responses: + * 200: + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + * items: + * allOf: + * - $ref: "#/components/schemas/PatientRequiredMatchingAttributes" + */ +// router.get( +// "/patients", +// require("./controller/QIDO-RS/allPatient") +// ); + +//#endregion + +module.exports = router; \ No newline at end of file diff --git a/models/sql/init.js b/models/sql/init.js index 850bdc39..0e707251 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -28,7 +28,7 @@ async function init() { }); //TODO: 設計完畢後要將 force 刪除 - await sequelizeInstance.sync({force: true}); + await sequelizeInstance.sync(); } catch (e) { console.error('Unable to connect to the database:', e); process.exit(1); diff --git a/routes.js b/routes.js index 70fa56ea..7328bc31 100644 --- a/routes.js +++ b/routes.js @@ -20,7 +20,7 @@ module.exports = function (app) { loadAllPlugin(); app.use("/dicom-web", require("./api-sql/dicom-web/stow-rs.route")); - app.use("/dicom-web", require("./api/dicom-web/qido-rs.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-rendered.route")); From 9a8ac010bd8ab24fe29fb7bd6cc32d3dddffb919 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 2 Aug 2023 13:04:04 +0800 Subject: [PATCH 014/365] build: update .env template - replace mongodb to sql --- .env.template | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.env.template b/.env.template index 1daa7920..a44dc12a 100644 --- a/.env.template +++ b/.env.template @@ -1,12 +1,10 @@ -# MongoDB -MONGODB_NAME="raccoon-dicom" -MONGODB_HOSTS=["mongodb"] -MONGODB_PORTS=[27017] -MONGODB_USER="root" -MONGODB_PASSWORD="root" -MONGODB_AUTH_SOURCE="admin" -MONGODB_OPTIONS="" -MONGODB_IS_SHARDING_MODE=false +# SQL +SQL_HOST="127.0.0.1" +SQL_PORT="5432" +SQL_DB="raccoon" +SQL_TYPE="postgres" +SQL_USERNAME="postgres" +SQL_PASSWORD="postgres" # Server SERVER_PORT=8081 From dda14adfe675f8f768c025a3e8f4b2f6cbc26f30 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 2 Aug 2023 15:59:47 +0800 Subject: [PATCH 015/365] feat(pg): create db when not exists --- models/sql/init.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/models/sql/init.js b/models/sql/init.js index 0e707251..6d79afc0 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -4,10 +4,41 @@ const { StudyModel } = require("./models/study.model"); const { SeriesModel } = require("./models/series.model"); const { InstanceModel } = require("./models/instance.mode"); const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); +const { raccoonConfig } = require("@root/config-class"); const sequelizeInstance = require("./instance"); +async function initDatabasePostgres() { + const { Client } = require("pg"); + const client = new Client({ + user: raccoonConfig.sqlDbConfig.username, + password: raccoonConfig.sqlDbConfig.password, + host: raccoonConfig.sqlDbConfig.host, + port: raccoonConfig.sqlDbConfig.port, + database: "postgres" + }); + + await client.connect(); + + try { + let result = await client.query(`SELECT 'CREATE DATABASE ${raccoonConfig.sqlDbConfig.database}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${raccoonConfig.sqlDbConfig.database}')`); + if (result.rowCount > 0 ) { + await client.query(`CREATE DATABASE ${raccoonConfig.sqlDbConfig.database}`); + } + } catch(e) { + console.error(e); + process.exit(1); + } finally { + await client.end(); + } +} + async function init() { + + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + await initDatabasePostgres(); + } + try { await sequelizeInstance.authenticate(); From 72566caa4c3fedc230965304d1026f662eb613d6 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 2 Aug 2023 16:24:31 +0800 Subject: [PATCH 016/365] feat: add time query --- .../QIDO-RS/service/querybuilder.js | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 3c13fac9..5084c354 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -2,6 +2,7 @@ const _ = require("lodash"); const moment = require("moment"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { Op } = require("sequelize"); +const { raccoonConfig } = require("@root/config-class"); class BaseQueryBuilder { constructor(queryOptions) { @@ -60,13 +61,13 @@ class BaseQueryBuilder { if (dashIndex === 0) { // -YYYYMMDD return { [`x${tag}`]: { - [Op.lte]: this.dateStringToSqlDateOnly(value) + [Op.lte]: this.dateStringToSqlDateOnly(value.substring(1)) } }; } else if (dashIndex === value.length - 1) { // YYYYMMDD- return { [`x${tag}`]: { - [Op.gte]: this.dateStringToSqlDateOnly(value) + [Op.gte]: this.dateStringToSqlDateOnly(value.substring(0, dashIndex)) } }; } else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD @@ -85,10 +86,66 @@ class BaseQueryBuilder { } } + /** + * + * @param {string} tag + * @param {string} value + */ + getTimeQuery(tag, value) { + let dashIndex = value.indexOf("-"); + if (dashIndex === 0) { // -HHmmss + return { + [`x${tag}`]: { + [Op.lte]: this.timeStringToSqlTimeDecimal(value.substring(1)) + } + }; + } else if (dashIndex === value.length - 1) { // HHmmss- + return { + [`x${tag}`]: { + [Op.gte]: this.timeStringToSqlTimeDecimal(value.substring(0, dashIndex)) + } + }; + } else if (dashIndex > 0) { // HHmmss-HHmmss + return { + [`x${tag}`]: { + [Op.and]: [ + { [Op.gte]: this.timeStringToSqlTimeDecimal(value.substring(0, dashIndex)) }, + { [Op.lte]: this.timeStringToSqlTimeDecimal(value.substring(dashIndex + 1)) } + ] + } + }; + } else { + return { + [`x${tag}`]: this.timeStringToSqlTimeDecimal(value) + }; + } + } + dateStringToSqlDateOnly(value) { return moment(value, "YYYYMMDD").format("YYYY-MM-DD"); } + /** + * + * @param {string} timeStr + */ + getTimePadding(timeStr) { + let hhmmssStr = timeStr.padEnd(6, "0"); + if (timeStr.length === 5) { + hhmmssStr.padStart(6, "0"); + } + return hhmmssStr; + } + + + timeStringToSqlTimeDecimal(value) { + let hhmmssStr = this.getTimePadding(value); + if (hhmmssStr.includes(".")) { + let [timeStr, millionthSecondStr] = hhmmssStr.split("."); + hhmmssStr = this.getTimePadding(timeStr) + "." + millionthSecondStr; + } + return parseFloat(hhmmssStr); + } /** * @@ -163,6 +220,10 @@ class StudyQueryBuilder extends BaseQueryBuilder { this.mergeQuery(q); } + getStudyTime(value) { + let q = this.getTimeQuery(dictionary.keyword.StudyTime, value); + this.mergeQuery(q); + } } From 6a2fbf70b924f85a4cf857d02f4f96fd9d8c685c Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 2 Aug 2023 17:47:03 +0800 Subject: [PATCH 017/365] feat: add string json array query --- .../QIDO-RS/service/querybuilder.js | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 5084c354..f7d6e204 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const moment = require("moment"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { Op } = require("sequelize"); +const { Op, Sequelize, cast, col } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); class BaseQueryBuilder { @@ -27,8 +27,23 @@ class BaseQueryBuilder { }; } - getStringArrayQuery(tag, value) { - //TODO + getStringArrayJsonQuery(tag, value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + return { + [Op.or]: [ + Sequelize.where( + cast(Sequelize.col(`x${tag}`), "jsonb"), + { + [Op.contains]: cast(JSON.stringify([value]), "jsonb") + } + ) + ] + }; + } else { + return { + [Op.or]: [Sequelize.where(Sequelize.fn("JSON_CONTAINS", Sequelize.col(`x${tag}`), `[${value}]`))] + }; + } } getNumberQuery(tag, value) { @@ -170,9 +185,9 @@ class BaseQueryBuilder { * @see {@link https://stackoverflow.com/questions/60598225/how-to-merge-javascript-object-containing-symbols "How to merge javascript object containing symbols?"} */ mergeQuery(q) { - _.mergeWith(this.query, q, (a ,b) => { + _.mergeWith(this.query, q, (a, b) => { if (!_.isObject(b)) return b; - + return Array.isArray(a) ? [...a, ...b] : { ...a, ...b }; }); } @@ -224,6 +239,14 @@ class StudyQueryBuilder extends BaseQueryBuilder { let q = this.getTimeQuery(dictionary.keyword.StudyTime, value); this.mergeQuery(q); } + + getModalitiesInStudy(value) { + let q = this.getStringArrayJsonQuery(dictionary.keyword.ModalitiesInStudy, value); + this.query = { + ...this.query, + q + }; + } } From a893d1d9490719ca578235c534e3850171ef529e Mon Sep 17 00:00:00 2001 From: chin Date: Wed, 2 Aug 2023 22:15:46 +0800 Subject: [PATCH 018/365] feat: postgres use jsonb, otherwise json --- models/sql/models/instance.mode.js | 8 ++++---- models/sql/models/patient.model.js | 2 +- models/sql/models/series.model.js | 16 ++++++++-------- models/sql/models/study.model.js | 6 +++--- models/sql/vrTypeMapping.js | 4 +++- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js index 2d06b99c..61d40b47 100644 --- a/models/sql/models/instance.mode.js +++ b/models/sql/models/instance.mode.js @@ -34,10 +34,10 @@ InstanceModel.init({ type: vrTypeMapping.IS }, "x0040A043": { //SQ - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x0040A073": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x0040A491": { type: vrTypeMapping.CS @@ -46,10 +46,10 @@ InstanceModel.init({ type: vrTypeMapping.CS }, "x0040A730": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "json": { - type: DataTypes.JSON + type: vrTypeMapping.JSON } }, { sequelize: sequelizeInstance, diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index bbd16761..9ce68d29 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -38,7 +38,7 @@ PatientModel.init({ type: vrTypeMapping.UI }, "json": { - type: DataTypes.JSON + type: vrTypeMapping.JSON } }, { sequelize: sequelizeInstance, diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 86194e04..8bfedea6 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -27,22 +27,22 @@ SeriesModel.init({ type: vrTypeMapping.LO }, "x0008103F": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00081050": { // 1-n - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00081052": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00081070": { // 1-n - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00081072": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00081250": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00200011": { type: vrTypeMapping.IS @@ -54,13 +54,13 @@ SeriesModel.init({ type: vrTypeMapping.TM }, "x00400275": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00080031": { type: vrTypeMapping.TM }, "json": { - type: DataTypes.JSON + type: vrTypeMapping.JSON } }, { sequelize: sequelizeInstance, diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index fd224808..5a0039b6 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -15,7 +15,7 @@ StudyModel.init({ allowNull: false }, "x00080005": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00080020": { type: vrTypeMapping.DA @@ -30,7 +30,7 @@ StudyModel.init({ type: vrTypeMapping.CS }, "x00080061": { - type: DataTypes.JSON + type: vrTypeMapping.JSON }, "x00080090": { type: vrTypeMapping.PN @@ -53,7 +53,7 @@ StudyModel.init({ type: vrTypeMapping.IS }, "json": { - type: DataTypes.JSON + type: vrTypeMapping.JSON } }, { sequelize: sequelizeInstance, diff --git a/models/sql/vrTypeMapping.js b/models/sql/vrTypeMapping.js index 65f19dc2..71d7c544 100644 --- a/models/sql/vrTypeMapping.js +++ b/models/sql/vrTypeMapping.js @@ -1,3 +1,4 @@ +const { raccoonConfig } = require("@root/config-class"); const { DataTypes } = require("sequelize"); const vrTypeMapping = { @@ -31,7 +32,8 @@ const vrTypeMapping = { "UR": DataTypes.TEXT("long"), "US": DataTypes.SMALLINT.UNSIGNED, "UT": DataTypes.TEXT("long"), - "UV": DataTypes.BIGINT.UNSIGNED + "UV": DataTypes.BIGINT.UNSIGNED, + "JSON": raccoonConfig.sqlDbConfig.dialect === "postgres" ? DataTypes.JSONB : DataTypes.JSON // For Array or SQ data }; From 70153a275373d66412d603856a1f2a82c8c494ae Mon Sep 17 00:00:00 2001 From: chin Date: Wed, 2 Aug 2023 22:22:09 +0800 Subject: [PATCH 019/365] feat: remove cast column to jsonb - Currently, we default using jsonb in postgres --- .../controller/QIDO-RS/service/querybuilder.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index f7d6e204..2b3b8c70 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -30,14 +30,9 @@ class BaseQueryBuilder { getStringArrayJsonQuery(tag, value) { if (raccoonConfig.sqlDbConfig.dialect === "postgres") { return { - [Op.or]: [ - Sequelize.where( - cast(Sequelize.col(`x${tag}`), "jsonb"), - { - [Op.contains]: cast(JSON.stringify([value]), "jsonb") - } - ) - ] + [`x${tag}`]: { + [Op.contains]: cast(JSON.stringify([value]), "jsonb") + } }; } else { return { @@ -244,7 +239,7 @@ class StudyQueryBuilder extends BaseQueryBuilder { let q = this.getStringArrayJsonQuery(dictionary.keyword.ModalitiesInStudy, value); this.query = { ...this.query, - q + ...q }; } } From 73d8e97e58a2ff1eae50f1d21c4f9d593815c49a Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 3 Aug 2023 02:56:21 +0800 Subject: [PATCH 020/365] feat: support wildcard search of string querying --- .../QIDO-RS/service/querybuilder.js | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 2b3b8c70..3a06d7ea 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -21,7 +21,20 @@ class BaseQueryBuilder { return $or; } + /** + * + * @param {string} tag + * @param {string} value + * @returns + */ getStringQuery(tag, value) { + if (value.includes("%") || value.includes("_")) { + return { + [`x${tag}`]: { + [Op.like]: value + } + }; + } return { [`x${tag}`]: value }; @@ -47,11 +60,27 @@ class BaseQueryBuilder { }; } - getNumberArrayQuery(tag, value) { - //TODO - } - + /** + * + * @param {string} value + * @returns + */ getPersonNameQuery(value) { + if (value.includes("%") || value.includes("_")) { + return { + [Op.or]: { + [Op.like]: { + alphabetic: value + }, + [Op.like]: { + ideographic: value + }, + [Op.like]: { + phonetic: value + } + } + }; + } return { [Op.or]: { alphabetic: value, @@ -235,6 +264,14 @@ class StudyQueryBuilder extends BaseQueryBuilder { this.mergeQuery(q); } + getAccessionNumber(value) { + let q = this.getStringQuery(dictionary.keyword.AccessionNumber, value); + this.query = { + ...this.query, + ...q + }; + } + getModalitiesInStudy(value) { let q = this.getStringArrayJsonQuery(dictionary.keyword.ModalitiesInStudy, value); this.query = { From 7ac8ace2a06120dc053057673a83e0658e2e9447 Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 3 Aug 2023 15:56:06 +0800 Subject: [PATCH 021/365] feat(study): complete person name query - Modify the compose way of person name queries - Have a query and field (may be used later to distinguish fields) attrs - The outer structure should be Field: Query Method - Modify the content returned by querybuilder to sequelize's query Option - Patient Name needs to join with Patient and Person name for querying - Add ReferringPhysicianName foreign key to Study - ReferringPhysicianName query by joining only with Person Name --- .../QIDO-RS/service/QIDO-RS.service.js | 4 +- .../QIDO-RS/service/querybuilder.js | 109 ++++++++++++++---- models/sql/init.js | 3 + 3 files changed, 91 insertions(+), 25 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index fd88c3bb..cefac29f 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -88,9 +88,9 @@ class QidoDicomJsonFactory { "study": async () => { // return await getStudyDicomJson(queryOptions); let queryBuilder = new StudyQueryBuilder(queryOptions); - queryBuilder.build(); + let q = queryBuilder.build(); let studies = await StudyModel.findAll({ - where: queryBuilder.query + ...q }); console.log(studies); }, diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 3a06d7ea..ab62fca6 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -3,6 +3,8 @@ const moment = require("moment"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { Op, Sequelize, cast, col } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); +const { PersonNameModel } = require("@models/sql/models/personName.model"); +const { PatientModel } = require("@models/sql/models/patient.model"); class BaseQueryBuilder { constructor(queryOptions) { @@ -65,28 +67,34 @@ class BaseQueryBuilder { * @param {string} value * @returns */ - getPersonNameQuery(value) { + getPersonNameQuery(tag, value) { if (value.includes("%") || value.includes("_")) { return { - [Op.or]: { - [Op.like]: { - alphabetic: value - }, - [Op.like]: { - ideographic: value - }, - [Op.like]: { - phonetic: value + query: { + [Op.or]: { + alphabetic: { + [Op.like]: value + }, + ideographic: { + [Op.like]: value + }, + phonetic: { + [Op.like]: value + } } - } + }, + field: tag }; } return { - [Op.or]: { - alphabetic: value, - ideographic: value, - phonetic: value - } + query: { + [Op.or]: { + alphabetic: value, + ideographic: value, + phonetic: value + } + }, + field: tag }; } @@ -236,32 +244,70 @@ class StudyQueryBuilder extends BaseQueryBuilder { } } } - console.log(this.query); + + let sequelizeQuery = { + where: this.query + }; + + let includesPersonNameQuery = this.getSequelizeIncludePersonNameQuery(); + if (includesPersonNameQuery.length > 0) { + _.set(sequelizeQuery, "include", includesPersonNameQuery); + } + return sequelizeQuery; + } + + getSequelizeIncludePersonNameQuery() { + let includes = []; + + for (let personNameQuery of this.personQuery) { + includes.push(personNameQuery); + } + return includes; } getStudyInstanceUID(value) { let q = this.getStringQuery(dictionary.keyword.StudyInstanceUID, value); - _.merge(this.query, q); + this.query = { + ...this.query, + ...q + }; } getPatientName(value) { - let q = this.getPersonNameQuery(value); - this.personQuery.push(q); + let q = this.getPersonNameQuery(dictionary.keyword.PatientName, value); + this.personQuery.push({ + model: PatientModel, + include: [{ + model: PersonNameModel, + where: q.query, + required: true + }], + required: true + }); } getPatientID(value) { let q = this.getStringQuery(dictionary.keyword.PatientID, value); - _.merge(this.query, q); + this.query = { + ...this.query, + ...q + }; } getStudyDate(value) { let q = this.getDateQuery(dictionary.keyword.StudyDate, value); - this.mergeQuery(q); + this.query = { + ...this.query, + ...q + }; } getStudyTime(value) { let q = this.getTimeQuery(dictionary.keyword.StudyTime, value); - this.mergeQuery(q); + this.query = { + ...this.query, + ...q + }; } getAccessionNumber(value) { @@ -279,6 +325,23 @@ class StudyQueryBuilder extends BaseQueryBuilder { ...q }; } + + getReferringPhysicianName(value) { + let q = this.getPersonNameQuery(dictionary.keyword.ReferringPhysicianName, value); + this.personQuery.push({ + model: PersonNameModel, + where: q.where, + required: true + }); + } + + getStudyID(value) { + let q = this.getStringQuery(dictionary.keyword.StudyID, value); + this.query = { + ...this.query, + ...this.q + }; + } } diff --git a/models/sql/init.js b/models/sql/init.js index 6d79afc0..f90ba1c6 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -49,6 +49,9 @@ async function init() { foreignKey: "x00100020", targetKey: "x00100020" }); + StudyModel.belongsTo(PersonNameModel, { + foreignKey: "x00080090" + }); SeriesModel.belongsTo(StudyModel, { foreignKey: "x0020000D", targetKey: "x0020000D" From fe04b33f7029eb93bf1bd996db4b0fa5ea345922 Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 3 Aug 2023 18:36:09 +0800 Subject: [PATCH 022/365] feat: study getDicomJson fn - Change the input parameter of updateModalitiesInStudy to study - Used for updating the modalitiesInStudy field in JSON - This fn use the `json` fields for return - Add getNumberOfStudyRelatedSeries function - Add getNumberOfStudyRelatedInstances function - The fields 00081190, 00201206, and 00201208 will be set in this fn --- .../QIDO-RS/service/QIDO-RS.service.js | 8 +- models/sql/dicom-json-model.js | 2 +- models/sql/models/study.model.js | 83 ++++++++++++++++--- 3 files changed, 74 insertions(+), 19 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index cefac29f..94e3271b 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -86,13 +86,7 @@ class QidoDicomJsonFactory { // return await getPatientDicomJson(queryOptions); }, "study": async () => { - // return await getStudyDicomJson(queryOptions); - let queryBuilder = new StudyQueryBuilder(queryOptions); - let q = queryBuilder.build(); - let studies = await StudyModel.findAll({ - ...q - }); - console.log(studies); + return await StudyModel.getDicomJson(queryOptions); }, "series": async () => { // return await getSeriesDicomJson(queryOptions); diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index 56f478bf..49a67755 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -41,7 +41,7 @@ class SqlDicomJsonModel extends DicomJsonModel { let storedSeries = await this.storeSeriesCollection(dicomJsonClone, storedStudy); await this.storeInstanceCollection(dicomJsonClone, storedSeries); - await StudyModel.updateModalitiesInStudy(storedStudy.x0020000D); + await StudyModel.updateModalitiesInStudy(storedStudy); } catch(e) { throw e; } diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index 5a0039b6..d4af1b06 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -3,8 +3,29 @@ const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { SeriesModel } = require("./series.model"); const _ = require("lodash"); +const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder"); +const { InstanceModel } = require("./instance.mode"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -class StudyModel extends Model { }; +class StudyModel extends Model { + async getNumberOfStudyRelatedSeries() { + let {count} = await SeriesModel.findAndCountAll({ + where: { + x0020000D: _.get(this.json, "0020000D.Value.0") + } + }); + return count; + } + + async getNumberOfStudyRelatedInstances() { + let {count} = await InstanceModel.findAndCountAll({ + where: { + x0020000D: _.get(this.json, "0020000D.Value.0") + } + }); + return count; + } +}; StudyModel.init({ "studyPath": { @@ -62,10 +83,10 @@ StudyModel.init({ freezeTableName: true }); -StudyModel.updateModalitiesInStudy = async function (studyInstanceUid) { +StudyModel.updateModalitiesInStudy = async function (study) { let seriesArray = await SeriesModel.findAll({ where: { - x0020000D: studyInstanceUid + x0020000D: study.x0020000D }, attributes: [ [Sequelize.fn("DISTINCT", Sequelize.col("x00080060")), "modality"] @@ -73,19 +94,59 @@ StudyModel.updateModalitiesInStudy = async function (studyInstanceUid) { }); let modalities = []; - for(let item of seriesArray) { - if (_.get(item, "dataValues.modality")) + for (let item of seriesArray) { + if (_.get(item, "dataValues.modality")) modalities.push(item.dataValues.modality); } - await StudyModel.update({ - x00080061: modalities - }, { - where: { - x0020000D: studyInstanceUid - + study.json = { + ...study.json, + "00080061": { + vr: "CS", + Value: modalities } + }; + study.x00080061 = modalities; + await study.save(); +}; + +StudyModel.getDicomJson = async function (queryOptions) { + let queryBuilder = new StudyQueryBuilder(queryOptions); + let q = queryBuilder.build(); + let studies = await StudyModel.findAll({ + ...q, + attributes: ["json"] }); + + + return await Promise.all(studies.map(async study => { + let numberOfStudyRelatedSeries = await study.getNumberOfStudyRelatedSeries(); + let numberOfStudyRelatedInstances = await study.getNumberOfStudyRelatedInstances(); + let { json } = study.toJSON(); + // Set Retrieve URL + _.set(json, dictionary.keyword.RetrieveURL, { + vr: dictionary.tagVR[dictionary.keyword.RetrieveURL].vr, + Value: [`${queryOptions.retrieveBaseUrl}/${_.get(json, "0020000D.Value.0")}`] + }); + + // Set number of Study related series + _.set(json, dictionary.keyword.NumberOfStudyRelatedSeries, { + vr: dictionary.tagVR[dictionary.keyword.NumberOfStudyRelatedSeries].vr, + Value: [ + numberOfStudyRelatedSeries.toString() + ] + }); + + // Set number of Study related instances + _.set(json, dictionary.keyword.NumberOfStudyRelatedInstances, { + vr: dictionary.tagVR[dictionary.keyword.NumberOfStudyRelatedInstances].vr, + Value: [ + numberOfStudyRelatedInstances.toString() + ] + }); + + return json; + })); }; module.exports.StudyModel = StudyModel; From 1ba1706951a7fa83fc2d9b3d33979b4faede300c Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 3 Aug 2023 19:19:47 +0800 Subject: [PATCH 023/365] feat: add pagination in study getDicomJson --- models/sql/models/study.model.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index d4af1b06..85f6d939 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -115,7 +115,9 @@ StudyModel.getDicomJson = async function (queryOptions) { let q = queryBuilder.build(); let studies = await StudyModel.findAll({ ...q, - attributes: ["json"] + attributes: ["json"], + limit: queryOptions.limit, + offset: queryOptions.skip }); From 343e945e3b678959523bd0b22d3ffc07e9ceb487 Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 3 Aug 2023 21:17:45 +0800 Subject: [PATCH 024/365] fix: incorrect store json in series --- models/sql/po/series.po.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index b39d8e5e..0c717027 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -9,7 +9,10 @@ class SeriesPersistentObject { constructor(dicomJson, study) { this.json = {}; - Object.keys(tagsNeedStore.Study).forEach(key => { + Object.keys({ + ...tagsNeedStore.Study, + ...tagsNeedStore.Series + }).forEach(key => { let value = _.get(dicomJson, key); value ? _.set(this.json, key, value) : undefined; }); From 4da907d532de8a870773ec41a827e8743721e341 Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 3 Aug 2023 23:30:50 +0800 Subject: [PATCH 025/365] feat: query all series API --- .../controller/QIDO-RS/queryAllSeries.js | 47 +++++++++++++++++++ .../QIDO-RS/service/QIDO-RS.service.js | 3 +- api-sql/dicom-web/qido-rs.route.js | 12 ++--- models/sql/models/series.model.js | 28 +++++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js new file mode 100644 index 00000000..9ebca4e2 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js @@ -0,0 +1,47 @@ +const { + SqlQidoRsService: QidoRsService +} = require("./service/QIDO-RS.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class QueryAllSeriesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "QIDO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info("Query all series"); + + try { + + let qidoRsService = new QidoRsService(this.request, this.response, "series"); + + await qidoRsService.getAndResponseDicomJson(); + + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: errorStr + })); + } + } +} +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new QueryAllSeriesController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 94e3271b..78b9dff5 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -9,6 +9,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { StudyModel } = require("@models/sql/models/study.model"); +const { SeriesModel } = require("@models/sql/models/series.model"); class SqlQidoRsService extends QidoRsService { @@ -89,7 +90,7 @@ class QidoDicomJsonFactory { return await StudyModel.getDicomJson(queryOptions); }, "series": async () => { - // return await getSeriesDicomJson(queryOptions); + return await SeriesModel.getDicomJson(queryOptions); }, "instance": async () => { // return await getInstanceDicomJson(queryOptions); diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js index ea74e98c..21649cea 100644 --- a/api-sql/dicom-web/qido-rs.route.js +++ b/api-sql/dicom-web/qido-rs.route.js @@ -231,12 +231,12 @@ router.get("/studies", validateParams(queryValidation, "query", { * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" * */ -// router.get( -// "/series", validateParams(queryValidation, "query", { -// allowUnknown: true -// }), -// require("./controller/QIDO-RS/queryAllSeries") -// ); +router.get( + "/series", validateParams(queryValidation, "query", { + allowUnknown: true + }), + require("./controller/QIDO-RS/queryAllSeries") +); /** * @openapi diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 8bfedea6..92d377ff 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -1,6 +1,9 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); +const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder"); +const _ = require("lodash"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); class SeriesModel extends Model { }; @@ -69,4 +72,29 @@ SeriesModel.init({ freezeTableName: true }); +SeriesModel.getDicomJson = async function(queryOptions) { + let queryBuilder = new SeriesQueryBuilder(queryOptions); + let q = queryBuilder.build(); + let seriesArray = await SeriesModel.findAll({ + ...q, + attributes: ["json"], + limit: queryOptions.limit, + offset: queryOptions.skip + }); + + return await Promise.all(seriesArray.map(async series => { + let { json } = series.toJSON(); + // Set Retrieve URL + let studyInstanceUID = _.get(json, "0020000D.Value.0"); + let seriesInstanceUID = _.get(json, "0020000E.Value.0"); + _.set(json, dictionary.keyword.RetrieveURL, { + vr: dictionary.tagVR[dictionary.keyword.RetrieveURL].vr, + Value: [ + `${queryOptions.retrieveBaseUrl}/${studyInstanceUID}/series/${seriesInstanceUID}` + ] + }); + return json; + })); +}; + module.exports.SeriesModel = SeriesModel; From c3f604d3c8f45497d71195345efdc761d6532b44 Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 3 Aug 2023 23:31:20 +0800 Subject: [PATCH 026/365] feat(WIP): series query builder --- .../controller/QIDO-RS/seriesQueryBuilder.js | 68 +++++++++++++++++++ .../QIDO-RS/service/querybuilder.js | 53 +++++++++++++++ models/sql/po/series.po.js | 2 +- 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder.js new file mode 100644 index 00000000..232917de --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder.js @@ -0,0 +1,68 @@ +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { BaseQueryBuilder } = require("./service/querybuilder"); +const { Op, Sequelize } = require("sequelize"); +const { raccoonConfig } = require("@root/config-class"); + +class SeriesQueryBuilder extends BaseQueryBuilder { + getSeriesDate(value) { + let q = this.getDateQuery(dictionary.keyword.SeriesDate, value); + this.query = { + ...this.query, + ...q + }; + } + getModality(value) { + let q = this.getStringQuery(dictionary.keyword.Modality, value); + this.query = { + ...this.query, + ...q + }; + } + getSeriesDescription(value) { + let q = this.getStringQuery(dictionary.keyword.SeriesDescription, value); + this.query = { + ...this.query, + ...q + }; + } + + getPersonNameJsonArrayQuery(tag, value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.getWildCardRegexString(value); + return { + [Op.or]: [ + Sequelize.literal(`"x${tag}" @? '$[*].Alphabetic ? (@ like_regex "${value}" flag "is")'`) + ] + }; + } + throw new Error("Not implemented"); + } + + getPerformingPhysicianName(value) { + let q = this.getPersonNameJsonArrayQuery(dictionary.keyword.PerformingPhysicianName, value); + this.query = { + ...this.query, + ...q + }; + } + + getOperatorsName(value) { + let q = this.getPersonNameJsonArrayQuery(dictionary.keyword.OperatorsName, value); + this.query = { + ...this.query, + ...q + }; + } + + getSeriesNumber(value) { + let q = this.getStringQuery(dictionary.keyword.SeriesNumber, value); + this.query = { + ...this.query, + ...q + }; + } + + +} + +module.exports.SeriesQueryBuilder = SeriesQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index ab62fca6..56ddaf24 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -10,6 +10,45 @@ class BaseQueryBuilder { constructor(queryOptions) { this.queryOptions = queryOptions; this.personQuery = []; + this.bind = []; + } + + build() { + for (let key in this.queryOptions.query) { + let commaValue = this.comma(key, this.queryOptions.query[key]); + + for (let i = 0; i < commaValue.length; i++) { + let value = this.getWildCardQuery(commaValue[i][key]); + try { + this[`get${dictionary.tag[key]}`](value); + } catch (e) { + if (e.message.includes("not a function")) break; + } + } + } + + let sequelizeQuery = { + where: this.query + }; + + let includesPersonNameQuery = this.getSequelizeIncludePersonNameQuery(); + if (includesPersonNameQuery.length > 0) { + _.set(sequelizeQuery, "include", includesPersonNameQuery); + } + + if (this.bind.length > 0 ) { + _.set(sequelizeQuery, "bind", this.bind); + } + return sequelizeQuery; + } + + getSequelizeIncludePersonNameQuery() { + let includes = []; + + for (let personNameQuery of this.personQuery) { + includes.push(personNameQuery); + } + return includes; } comma(key, value) { @@ -211,6 +250,20 @@ class BaseQueryBuilder { return value; } + getWildCardRegexString(value) { + let wildCardIndex = value.indexOf("%"); + let questionIndex = value.indexOf("_"); + + if (wildCardIndex >= 0 || questionIndex >= 0) { + value = value.replace(/%/gm, ".*"); + value = value.replace(/_/gm, "."); + value = value.replace(/\^/gm, "\\^"); + value = "^" + value; + } + + return value; + } + /** * * @param {*} q diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index 0c717027..dd9a8de9 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -26,7 +26,7 @@ class SeriesPersistentObject { this.x0008103E = _.get(dicomJson, "0008103E.Value.0", ""); this.x0008103F = _.get(dicomJson, "0008103F.Value", undefined); this.x00081050 = _.get(dicomJson, "00081050.Value", ""); - this.x00081052 = _.get(dicomJson, "00081052.Value", ""); + this.x00081052 = _.get(dicomJson, "00081052.Value.0", ""); this.x00081070 = _.get(dicomJson, "00081070.Value", ""); this.x00081072 = _.get(dicomJson, "00081072.Value", ""); this.x00081250 = _.get(dicomJson, "00081250.Value", ""); From a85178145c7bc7de38f4768296379c18d8a1c82d Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 12:01:55 +0800 Subject: [PATCH 027/365] refactor: using count instead of findAllAndCount - Reduce unnecessary find queries --- models/sql/models/study.model.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index 85f6d939..cd6ddd10 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -9,7 +9,7 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); class StudyModel extends Model { async getNumberOfStudyRelatedSeries() { - let {count} = await SeriesModel.findAndCountAll({ + let count = await SeriesModel.count({ where: { x0020000D: _.get(this.json, "0020000D.Value.0") } @@ -18,7 +18,7 @@ class StudyModel extends Model { } async getNumberOfStudyRelatedInstances() { - let {count} = await InstanceModel.findAndCountAll({ + let count = await InstanceModel.count({ where: { x0020000D: _.get(this.json, "0020000D.Value.0") } From bf117fe39f35c2d9cfd39ba446b19bf3e43e94d9 Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 12:03:34 +0800 Subject: [PATCH 028/365] feat: study model to be one to many association --- models/sql/init.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/models/sql/init.js b/models/sql/init.js index f90ba1c6..b33dc380 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -52,6 +52,11 @@ async function init() { StudyModel.belongsTo(PersonNameModel, { foreignKey: "x00080090" }); + + StudyModel.hasMany(SeriesModel, { + foreignKey: "x0020000D", + sourceKey: "x0020000D" + }); SeriesModel.belongsTo(StudyModel, { foreignKey: "x0020000D", targetKey: "x0020000D" From 77e149dd5c379eb51ef5e6ddf807e5626fe8e2db Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 12:14:55 +0800 Subject: [PATCH 029/365] refactor: move seriesQueryBuilder into service dir --- .../controller/QIDO-RS/{ => service}/seriesQueryBuilder.js | 2 +- models/sql/models/series.model.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename api-sql/dicom-web/controller/QIDO-RS/{ => service}/seriesQueryBuilder.js (96%) diff --git a/api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js similarity index 96% rename from api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder.js rename to api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index 232917de..0d73a683 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -1,5 +1,5 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { BaseQueryBuilder } = require("./service/querybuilder"); +const { BaseQueryBuilder } = require("./querybuilder"); const { Op, Sequelize } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 92d377ff..3e012e40 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -1,7 +1,7 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); -const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/seriesQueryBuilder"); +const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder"); const _ = require("lodash"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); From c185a7f30bf70fd32780bac4411fb953433ce1dd Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 12:19:45 +0800 Subject: [PATCH 030/365] feat: change `getModalitiesInStudy` query way - Join the Series table to query modality - rename personQuery to includeQueries - Because of it always storing include query in array --- .../QIDO-RS/service/querybuilder.js | 68 ++++++------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 56ddaf24..a93f94b3 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -5,11 +5,13 @@ const { Op, Sequelize, cast, col } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); const { PersonNameModel } = require("@models/sql/models/personName.model"); const { PatientModel } = require("@models/sql/models/patient.model"); +const sequelize = require("@models/sql/instance"); class BaseQueryBuilder { constructor(queryOptions) { this.queryOptions = queryOptions; - this.personQuery = []; + /** @type {import("sequelize").IncludeOptions} */ + this.includeQueries = []; this.bind = []; } @@ -36,17 +38,17 @@ class BaseQueryBuilder { _.set(sequelizeQuery, "include", includesPersonNameQuery); } - if (this.bind.length > 0 ) { + if (this.bind.length > 0) { _.set(sequelizeQuery, "bind", this.bind); } return sequelizeQuery; } - + getSequelizeIncludePersonNameQuery() { let includes = []; - for (let personNameQuery of this.personQuery) { - includes.push(personNameQuery); + for (let includeQuery of this.includeQueries) { + includes.push(includeQuery); } return includes; } @@ -253,14 +255,14 @@ class BaseQueryBuilder { getWildCardRegexString(value) { let wildCardIndex = value.indexOf("%"); let questionIndex = value.indexOf("_"); - + if (wildCardIndex >= 0 || questionIndex >= 0) { value = value.replace(/%/gm, ".*"); value = value.replace(/_/gm, "."); value = value.replace(/\^/gm, "\\^"); value = "^" + value; } - + return value; } @@ -284,39 +286,6 @@ class StudyQueryBuilder extends BaseQueryBuilder { this.query = {}; } - build() { - for (let key in this.queryOptions.query) { - let commaValue = this.comma(key, this.queryOptions.query[key]); - - for (let i = 0; i < commaValue.length; i++) { - let value = this.getWildCardQuery(commaValue[i][key]); - try { - this[`get${dictionary.tag[key]}`](value); - } catch (e) { - if (e.message.includes("not a function")) break; - } - } - } - - let sequelizeQuery = { - where: this.query - }; - - let includesPersonNameQuery = this.getSequelizeIncludePersonNameQuery(); - if (includesPersonNameQuery.length > 0) { - _.set(sequelizeQuery, "include", includesPersonNameQuery); - } - return sequelizeQuery; - } - - getSequelizeIncludePersonNameQuery() { - let includes = []; - - for (let personNameQuery of this.personQuery) { - includes.push(personNameQuery); - } - return includes; - } getStudyInstanceUID(value) { let q = this.getStringQuery(dictionary.keyword.StudyInstanceUID, value); @@ -328,7 +297,7 @@ class StudyQueryBuilder extends BaseQueryBuilder { getPatientName(value) { let q = this.getPersonNameQuery(dictionary.keyword.PatientName, value); - this.personQuery.push({ + this.includeQueries.push({ model: PatientModel, include: [{ model: PersonNameModel, @@ -372,16 +341,19 @@ class StudyQueryBuilder extends BaseQueryBuilder { } getModalitiesInStudy(value) { - let q = this.getStringArrayJsonQuery(dictionary.keyword.ModalitiesInStudy, value); - this.query = { - ...this.query, - ...q - }; + let q = this.getStringQuery(dictionary.keyword.Modality, value); + this.includeQueries.push({ + model: sequelize.model("Series"), + where: { + ...q + }, + attributes: [] + }); } getReferringPhysicianName(value) { let q = this.getPersonNameQuery(dictionary.keyword.ReferringPhysicianName, value); - this.personQuery.push({ + this.includeQueries.push({ model: PersonNameModel, where: q.where, required: true @@ -392,7 +364,7 @@ class StudyQueryBuilder extends BaseQueryBuilder { let q = this.getStringQuery(dictionary.keyword.StudyID, value); this.query = { ...this.query, - ...this.q + ...q }; } } From d366177a01e1e4de56ede5902f73fada101d0e8f Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 12:21:10 +0800 Subject: [PATCH 031/365] refactor: remove unused x00080061 in study model --- models/sql/models/study.model.js | 4 ---- models/sql/po/study.po.js | 2 -- 2 files changed, 6 deletions(-) diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index cd6ddd10..706b2be6 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -50,9 +50,6 @@ StudyModel.init({ "x00080056": { type: vrTypeMapping.CS }, - "x00080061": { - type: vrTypeMapping.JSON - }, "x00080090": { type: vrTypeMapping.PN }, @@ -106,7 +103,6 @@ StudyModel.updateModalitiesInStudy = async function (study) { Value: modalities } }; - study.x00080061 = modalities; await study.save(); }; diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js index 8deb7d3b..267065b9 100644 --- a/models/sql/po/study.po.js +++ b/models/sql/po/study.po.js @@ -22,7 +22,6 @@ class StudyPersistentObject { this.x00080030 = _.get(dicomJson, "00080030.Value.0", ""); this.x00080050 = _.get(dicomJson, "00080050.Value.0", ""); this.x00080056 = _.get(dicomJson, "00080056.Value.0", ""); - this.x00080061 = _.get(dicomJson, "00080061.Value.0", undefined); this.x00080090 = _.get(dicomJson, "00080090.Value.0", ""); this.x00080201 = _.get(dicomJson, "00080201.Value.0", ""); this.x0020000D = _.get(dicomJson, "0020000D.Value.0", ""); @@ -55,7 +54,6 @@ class StudyPersistentObject { x00080030 : this.x00080030 ? Number(this.x00080030) : undefined, x00080050 : this.x00080050, x00080056 : this.x00080056, - x00080061 : this.x00080061, x00080201 : this.x00080201, x0020000D : this.x0020000D, x00200010 : this.x00200010, From 37b6342c888cebeaebafe89d6b437dfe94831bfa Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 12:59:00 +0800 Subject: [PATCH 032/365] refactor: remove unused column in patient po --- models/sql/po/patient.po.js | 1 - 1 file changed, 1 deletion(-) diff --git a/models/sql/po/patient.po.js b/models/sql/po/patient.po.js index dfcd6da2..c766b5c4 100644 --- a/models/sql/po/patient.po.js +++ b/models/sql/po/patient.po.js @@ -12,7 +12,6 @@ class PatientPersistentObject { let value = _.get(dicomJson, key); value ? _.set(this.json, key, value) : undefined; }); - this.x00100010 = _.get(dicomJson, "00100010.Value.0", ""); this.x00100020 = _.get(dicomJson, "00100020.Value.0", ""); this.x00100021 = _.get(dicomJson, "00100021.Value.0", ""); this.x00100030 = _.get(dicomJson, "00100030.Value.0", ""); From f48a5c0b47e2355bc20fc43e4fa936e005e40fb5 Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 16:43:56 +0800 Subject: [PATCH 033/365] fix: query param to dicom tag wrong # Problems - In SQL, we only need tag.tag of SQ value - The converted tag must be tag.tag, now it is incorrect # Solution - The pushing item missing index, add it - remove `.Value` --- .../controller/QIDO-RS/service/QIDO-RS.service.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 78b9dff5..99a29d9b 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -120,7 +120,7 @@ function convertAllQueryToDicomTag(iParam) { if (dictionary.keyword[keyNameSplit[x]]) { newKeyNames.push(dictionary.keyword[keyNameSplit[x]]); } else if (dictionary.tag[keyNameSplit[x]]) { - newKeyNames.push(keyNameSplit); + newKeyNames.push(keyNameSplit[x]); } } if (newKeyNames.length === 0) { @@ -129,9 +129,8 @@ function convertAllQueryToDicomTag(iParam) { `Invalid request query: ${keyNameSplit}`, 400 ); - } else if (newKeyNames.length >= 2) { - newKeyNames.push("Value"); - } + } + let retKeyName = newKeyNames.join("."); newQS[retKeyName] = iParam[keyName]; } From b454036fd705c96cad94052aaa7accf308f4ee44 Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 18:40:39 +0800 Subject: [PATCH 034/365] feat: add series many to many person name model - PerformingPhysicianName - OperatorsName --- models/sql/init.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/models/sql/init.js b/models/sql/init.js index b33dc380..3290eca0 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -45,10 +45,12 @@ async function init() { PatientModel.belongsTo(PersonNameModel, { foreignKey: "x00100010" }); + StudyModel.belongsTo(PatientModel, { foreignKey: "x00100020", targetKey: "x00100020" }); + StudyModel.belongsTo(PersonNameModel, { foreignKey: "x00080090" }); @@ -61,6 +63,29 @@ async function init() { foreignKey: "x0020000D", targetKey: "x0020000D" }); + + // Performing Physician Name many to many + SeriesModel.belongsToMany(PersonNameModel, { + through: "PerformingPhysicianName", + as: "performingPhysicianName", + sourceKey: "x0020000E", + foreignKey: "x0020000E" + }); + PersonNameModel.belongsToMany(SeriesModel, { + through: "PerformingPhysicianName" + }); + + // Operator's Name many to many + SeriesModel.belongsToMany(PersonNameModel, { + through: "OperatorsName", + as: "operatorsName", + sourceKey: "x0020000E", + foreignKey: "x0020000E" + }); + PersonNameModel.belongsToMany(SeriesModel, { + through: "OperatorsName" + }); + InstanceModel.belongsTo(SeriesModel, { foreignKey: "x0020000E", targetKey: "x0020000E" From e567951a08b2614c35196c6deddb9ca8cd0a9d50 Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 18:41:34 +0800 Subject: [PATCH 035/365] feat: series create person names when creation - create Operators' Name - create Performing Physician's Name --- models/sql/po/series.po.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index dd9a8de9..fde82a73 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -33,7 +33,7 @@ class SeriesPersistentObject { this.x00200011 = _.get(dicomJson, "00200011.Value.0", ""); this.x00400244 = _.get(dicomJson, "00400244.Value.0", undefined); this.x00400245 = _.get(dicomJson, "00400245.Value.0", ""); - this.x00400275 = _.get(dicomJson, "00400275.Value", ""); + this.x00400275 = _.get(dicomJson, "00400275.Value.0", ""); this.x00080031 = _.get(dicomJson, "00080031.Value.0", ""); } @@ -48,6 +48,35 @@ class SeriesPersistentObject { return undefined; } + async createPersonNames(field) { + let personNames = []; + if (this[field]) { + for (let personName of this[field]) { + let personNameSequelize = await PersonNameModel.create({ + alphabetic: _.get(personName, "Alphabetic", undefined), + ideographic: _.get(personName, "Ideographic", undefined), + phonetic: _.get(personName, "Phonetic", undefined) + }); + personNames.push(personNameSequelize); + } + } + return personNames; + } + + async addPerformingPhysicianNames(series) { + let performingPhysicianNames = await this.createPersonNames("x00081050"); + for (let performingPhysicianName of performingPhysicianNames) { + await series.addPerformingPhysicianName(performingPhysicianName); + } + } + + async addOperatorsNames(series) { + let operationsNames = await this.createPersonNames("x00081070"); + for (let operationsName of operationsNames) { + await series.addOperatorsName(operationsName); + } + } + async createSeries() { let [series, created] = await SeriesModel.findOrCreate({ where: { @@ -76,6 +105,12 @@ class SeriesPersistentObject { } }); + if (created) { + await this.addPerformingPhysicianNames(series); + await this.addOperatorsNames(series); + await series.save(); + } + return series; } From f085f7c6a244300389f4c83902b2d9a948001ffc Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 18:42:48 +0800 Subject: [PATCH 036/365] feat: set UIDs of level to primary key --- models/sql/models/instance.mode.js | 3 ++- models/sql/models/series.model.js | 11 +++-------- models/sql/models/study.model.js | 3 ++- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js index 61d40b47..187bb50d 100644 --- a/models/sql/models/instance.mode.js +++ b/models/sql/models/instance.mode.js @@ -19,7 +19,8 @@ InstanceModel.init({ "x00080018": { type: vrTypeMapping.UI, allowNull: false, - unique: true + unique: true, + primaryKey: true }, "x00080016": { type: vrTypeMapping.UI diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 3e012e40..b35e6ede 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -18,7 +18,8 @@ SeriesModel.init({ "x0020000E": { type: vrTypeMapping.UI, allowNull: false, - unique: true + unique: true, + primaryKey: true }, "x00080021": { type: vrTypeMapping.DA @@ -32,15 +33,9 @@ SeriesModel.init({ "x0008103F": { type: vrTypeMapping.JSON }, - "x00081050": { // 1-n - type: vrTypeMapping.JSON - }, "x00081052": { type: vrTypeMapping.JSON }, - "x00081070": { // 1-n - type: vrTypeMapping.JSON - }, "x00081072": { type: vrTypeMapping.JSON }, @@ -77,7 +72,7 @@ SeriesModel.getDicomJson = async function(queryOptions) { let q = queryBuilder.build(); let seriesArray = await SeriesModel.findAll({ ...q, - attributes: ["json"], + attributes: ["json", "x0020000E"], limit: queryOptions.limit, offset: queryOptions.skip }); diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index 706b2be6..ae49f005 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -59,7 +59,8 @@ StudyModel.init({ "x0020000D": { type: vrTypeMapping.UI, allowNull: false, - unique: true + unique: true, + primaryKey: true }, "x00200010": { type: vrTypeMapping.SH From 4740615674a2e4633d115faec628e55edef5d1d8 Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 18:45:30 +0800 Subject: [PATCH 037/365] feat: complete series query builder --- .../QIDO-RS/service/querybuilder.js | 6 +- .../QIDO-RS/service/seriesQueryBuilder.js | 173 ++++++++++++++++-- 2 files changed, 167 insertions(+), 12 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index a93f94b3..a2316107 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -22,7 +22,11 @@ class BaseQueryBuilder { for (let i = 0; i < commaValue.length; i++) { let value = this.getWildCardQuery(commaValue[i][key]); try { - this[`get${dictionary.tag[key]}`](value); + if (key.includes(".")) { + this[key](value); + } else { + this[`get${dictionary.tag[key]}`](value); + } } catch (e) { if (e.message.includes("not a function")) break; } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index 0d73a683..0c188740 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -2,8 +2,27 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { BaseQueryBuilder } = require("./querybuilder"); const { Op, Sequelize } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); +const _ = require("lodash"); +const { PersonNameModel } = require("@models/sql/models/personName.model"); +const sequelize = require("@models/sql/instance"); class SeriesQueryBuilder extends BaseQueryBuilder { + constructor(queryOptions) { + super(queryOptions); + let seriesRequestAttributeSequence = new SeriesRequestAttributeSequence(this); + this["00400275.00080050"] = SeriesRequestAttributeSequence.prototype.getAccessionNumber.bind(seriesRequestAttributeSequence); + this["00400275.00080051.00400031"] = SeriesRequestAttributeSequence.prototype.getIssuerLocalNameSpaceEntityID.bind(seriesRequestAttributeSequence); + this["00400275.00080051.00400032"] = SeriesRequestAttributeSequence.prototype.getIssuerUniversalEntityID.bind(seriesRequestAttributeSequence); + this["00400275.00080051.00400033"] = SeriesRequestAttributeSequence.prototype.getIssuerUniversalEntityIDType.bind(seriesRequestAttributeSequence); + this["00400275.00321033"] = SeriesRequestAttributeSequence.prototype.getRequestingService.bind(seriesRequestAttributeSequence); + this["00400275.00401001"] = SeriesRequestAttributeSequence.prototype.getRequestedProcedureID.bind(seriesRequestAttributeSequence); + this["00400275.0020000D"] = SeriesRequestAttributeSequence.prototype.getStudyInstanceUID.bind(seriesRequestAttributeSequence); + + this.includeQueries.push({ + model: sequelize.model("Study"), + attributes: [] + }); + } getSeriesDate(value) { let q = this.getDateQuery(dictionary.keyword.SeriesDate, value); this.query = { @@ -39,19 +58,27 @@ class SeriesQueryBuilder extends BaseQueryBuilder { } getPerformingPhysicianName(value) { - let q = this.getPersonNameJsonArrayQuery(dictionary.keyword.PerformingPhysicianName, value); - this.query = { - ...this.query, - ...q - }; + let { query } = this.getPersonNameQuery(dictionary.keyword.PerformingPhysicianName, value); + this.includeQueries.push({ + model: PersonNameModel, + as: "performingPhysicianName", + where: { + ...query + }, + attributes: [] + }); } getOperatorsName(value) { - let q = this.getPersonNameJsonArrayQuery(dictionary.keyword.OperatorsName, value); - this.query = { - ...this.query, - ...q - }; + let { query } = this.getPersonNameQuery(dictionary.keyword.OperatorsName, value); + this.includeQueries.push({ + model: PersonNameModel, + as: "operatorsName", + where: { + ...query + }, + attributes: [] + }); } getSeriesNumber(value) { @@ -62,7 +89,131 @@ class SeriesQueryBuilder extends BaseQueryBuilder { }; } - + +} + +class SeriesRequestAttributeSequence { + constructor(seriesQueryBuilder) { + /** @type {SeriesQueryBuilder} */ + this.seriesQueryBuilder = seriesQueryBuilder; + } + getAccessionNumber(value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.seriesQueryBuilder.getWildCardRegexString(value); + let q = { + [Op.or]: [ + Sequelize.literal(`"x00400275" @? '$."00080050".Value[0] ? (@ like_regex "${value}")'`) + ] + }; + this.seriesQueryBuilder.query = { + ...this.seriesQueryBuilder.query, + ...q + }; + } + throw new Error("Not implemented"); + } + + getIssuerLocalNameSpaceEntityID(value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.seriesQueryBuilder.getWildCardRegexString(value); + let q = { + [Op.or]: [ + Sequelize.literal(`"x00400275" @? '$."00080051".Value[0]."00400031".Value[0] ? (@ like_regex "${value}")'`) + ] + }; + + this.seriesQueryBuilder.query = { + ...this.seriesQueryBuilder.query, + ...q + }; + } + + throw new Error("Not implemented"); + } + + getIssuerUniversalEntityID(value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.seriesQueryBuilder.getWildCardRegexString(value); + let q = { + [Op.or]: [ + Sequelize.literal(`"x00400275" @? '$."00080051".Value[0]."00400032".Value[0] ? (@ like_regex "${value}")'`) + ] + }; + + this.seriesQueryBuilder.query = { + ...this.seriesQueryBuilder.query, + ...q + }; + } + throw new Error("Not implemented"); + } + + getIssuerUniversalEntityIDType(value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.seriesQueryBuilder.getWildCardRegexString(value); + let q = { + [Op.or]: [ + Sequelize.literal(`"x00400275" @? '$."00080051".Value[0]."00400033".Value[0] ? (@ like_regex "${value}")'`) + ] + }; + + this.seriesQueryBuilder.query = { + ...this.seriesQueryBuilder.query, + ...q + }; + } + throw new Error("Not implemented"); + } + + getRequestingService(value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.seriesQueryBuilder.getWildCardRegexString(value); + let q = { + [Op.or]: [ + Sequelize.literal(`"x00400275" @? '$."00321033".Value[0] ? (@ like_regex "${value}")'`) + ] + }; + + this.seriesQueryBuilder.query = { + ...this.seriesQueryBuilder.query, + ...q + }; + } + throw new Error("Not implemented"); + } + + getRequestedProcedureID(value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.seriesQueryBuilder.getWildCardRegexString(value); + let q = { + [Op.or]: [ + Sequelize.literal(`"x00400275" @? '$."00401001".Value[0] ? (@ like_regex "${value}")'`) + ] + }; + this.seriesQueryBuilder.query = { + ...this.seriesQueryBuilder.query, + ...q + }; + } + throw new Error("Not implemented"); + } + + getStudyInstanceUID(value) { + if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + value = this.seriesQueryBuilder.getWildCardRegexString(value); + let q = { + [Op.or]: [ + Sequelize.literal(`"x00400275" @? '$."0020000D".Value[0] ? (@ like_regex "${value}")'`) + ] + }; + this.seriesQueryBuilder.query = { + ...this.seriesQueryBuilder.query, + ...q + }; + } + throw new Error("Not implemented"); + } + } module.exports.SeriesQueryBuilder = SeriesQueryBuilder; \ No newline at end of file From b9421710d2e4009383fa0d57244bd493aaf908bd Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 4 Aug 2023 20:02:29 +0800 Subject: [PATCH 038/365] feat: update exist item when store instance --- models/sql/po/instance.po.js | 44 ++++++++++++-------- models/sql/po/patient.po.js | 49 ++++++++++++++++------ models/sql/po/series.po.js | 79 +++++++++++++++++++++++++++--------- models/sql/po/study.po.js | 53 +++++++++++++++++------- 4 files changed, 162 insertions(+), 63 deletions(-) diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 4ff5c925..344946c9 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -69,30 +69,42 @@ class InstancePersistentObject { async createInstance() { + + let item = { + json: this.json, + x0020000D: this.x0020000D, + x0020000E: this.x0020000E, + x00080018: this.x00080018, + x00080016: this.x00080016, + x00080023: this.x00080023, + x00080033: this.x00080033 ? Number(this.x00080033) : undefined, + x00200013: this.x00200013, + x0040A043: this.x0040A043, + x0040A073: this.x0040A073, + x0040A491: this.x0040A491, + x0040A493: this.x0040A493, + x0040A730: this.x0040A730, + instancePath: this.instancePath + }; + let [instance, created] = await InstanceModel.findOrCreate({ where: { x0020000D: this.x0020000D, x0020000E: this.x0020000E, x00080018: this.x00080018 }, - defaults: { - json: this.json, - x0020000D: this.x0020000D, - x0020000E: this.x0020000E, - x00080018: this.x00080018, - x00080016: this.x00080016, - x00080023: this.x00080023, - x00080033: this.x00080033 ? Number(this.x00080033) : undefined, - x00200013: this.x00200013, - x0040A043: this.x0040A043, - x0040A073: this.x0040A073, - x0040A491: this.x0040A491, - x0040A493: this.x0040A493, - x0040A730: this.x0040A730, - instancePath: this.instancePath - } + defaults: item }); + if (created) { + // do nothing + } else { + await InstanceModel.update(item, { + where: { + x00080018: instance.dataValues.x00080018 + } + }); + } return instance; } diff --git a/models/sql/po/patient.po.js b/models/sql/po/patient.po.js index c766b5c4..2246c5e0 100644 --- a/models/sql/po/patient.po.js +++ b/models/sql/po/patient.po.js @@ -12,6 +12,7 @@ class PatientPersistentObject { let value = _.get(dicomJson, key); value ? _.set(this.json, key, value) : undefined; }); + this.x00100010 = _.get(dicomJson, "00100010.Value.0", undefined); this.x00100020 = _.get(dicomJson, "00100020.Value.0", ""); this.x00100021 = _.get(dicomJson, "00100021.Value.0", ""); this.x00100030 = _.get(dicomJson, "00100030.Value.0", ""); @@ -34,29 +35,53 @@ class PatientPersistentObject { return undefined; } + async updatePersonName(patient) { + if (this.x00100010) { + await PersonNameModel.update({ + alphabetic: _.get(this.x00100010, "Alphabetic", undefined), + ideographic: _.get(this.x00100010, "Ideographic", undefined), + phonetic: _.get(this.x00100010, "Phonetic", undefined) + }, { + where: { + id: patient.dataValues.x00100010 + } + }); + } + } + async createPatient() { + + let item = { + json: this.json, + x00100020: this.x00100020, + x00100021: this.x00100021, + x00100030: this.x00100030 ? this.x00100030 : undefined, + x00100032: this.x00100032 ? Number(this.x00100032) : undefined, + x00100040: this.x00100040, + x00102160: this.x00102160, + x00104000: this.x00104000, + x00880130: this.x00880130, + x00880140: this.x00880140 + }; + let [patient, created] = await PatientModel.findOrCreate({ where: { x00100020: this.x00100020 }, - defaults: { - json: this.json, - x00100020: this.x00100020, - x00100021: this.x00100021, - x00100030: this.x00100030 ? this.x00100030 : undefined, - x00100032: this.x00100032 ? Number(this.x00100032) : undefined, - x00100040: this.x00100040, - x00102160: this.x00102160, - x00104000: this.x00104000, - x00880130: this.x00880130, - x00880140: this.x00880140 - } + defaults: item }); if (created) { let personName = await this.createPersonName(); patient.x00100010 = personName ? personName.id : undefined; await patient.save(); + } else { + await PatientModel.update(item, { + where: { + id: patient.dataValues.id + } + }); + await this.updatePersonName(patient); } return patient; diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index fde82a73..41221346 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -4,6 +4,7 @@ const { PersonNameModel } = require("../models/personName.model"); const { SeriesModel } = require("../models/series.model"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); +const sequelize = require("../instance"); class SeriesPersistentObject { constructor(dicomJson, study) { @@ -70,6 +71,13 @@ class SeriesPersistentObject { } } + async updatePerformingPhysicianNames(series) { + for(let personName of series.performingPhysicianName) { + await personName.destroy(); + } + await this.addPerformingPhysicianNames(series); + } + async addOperatorsNames(series) { let operationsNames = await this.createPersonNames("x00081070"); for (let operationsName of operationsNames) { @@ -77,38 +85,69 @@ class SeriesPersistentObject { } } + async updateOperatorsNames(series) { + for(let personName of series.operatorsName) { + await personName.destroy(); + } + await this.addOperatorsNames(series); + } + async createSeries() { + let item = { + json: this.json, + x0020000D: this.x0020000D, + x0020000E: this.x0020000E, + x00080021: this.x00080021, + x00080060: this.x00080060, + x0008103E: this.x0008103E, + x0008103F: this.x0008103F, + x00081050: this.x00081050, + x00081052: this.x00081052, + x00081070: this.x00081070, + x00081072: this.x00081072, + x00081250: this.x00081250, + x00200011: this.x00200011, + x00400244: this.x00400244, + x00400245: this.x00400245 ? Number(this.x00400245) : undefined, + x00400275: this.x00400275, + x00080031: this.x00080031 ? Number(this.x00080031) : undefined, + seriesPath: this.seriesPath + }; + let [series, created] = await SeriesModel.findOrCreate({ where: { x0020000D: this.x0020000D, x0020000E: this.x0020000E }, - defaults: { - json: this.json, - x0020000D: this.x0020000D, - x0020000E: this.x0020000E, - x00080021: this.x00080021, - x00080060: this.x00080060, - x0008103E: this.x0008103E, - x0008103F: this.x0008103F, - x00081050: this.x00081050, - x00081052: this.x00081052, - x00081070: this.x00081070, - x00081072: this.x00081072, - x00081250: this.x00081250, - x00200011: this.x00200011, - x00400244: this.x00400244, - x00400245: this.x00400245 ? Number(this.x00400245) : undefined, - x00400275: this.x00400275, - x00080031: this.x00080031 ? Number(this.x00080031) : undefined, - seriesPath: this.seriesPath - } + defaults: item }); if (created) { await this.addPerformingPhysicianNames(series); await this.addOperatorsNames(series); await series.save(); + } else { + await SeriesModel.update(item, { + where: { + x0020000E: series.dataValues.x0020000E + } + }); + let seriesWithIncludeItem = await SeriesModel.findByPk(series.dataValues.x0020000E, { + include: [ + { + model: sequelize.model("PersonName"), + as: "performingPhysicianName", + attributes: ["id"] + }, + { + model: sequelize.model("PersonName"), + as: "operatorsName", + attributes: ["id"] + } + ], + attributes: ["x0020000E"] + }); + await this.updatePerformingPhysicianNames(seriesWithIncludeItem); } return series; diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js index 267065b9..81b87566 100644 --- a/models/sql/po/study.po.js +++ b/models/sql/po/study.po.js @@ -41,32 +41,55 @@ class StudyPersistentObject { return undefined; } + async updateReferringPhysicianName(study) { + if (this.x00080090) { + await PersonNameModel.update({ + alphabetic: _.get(this.x00080090, "Alphabetic", undefined), + ideographic: _.get(this.x00080090, "Ideographic", undefined), + phonetic: _.get(this.x00080090, "Phonetic", undefined) + }, { + where: { + id: study.dataValues.x00080090 + } + }); + } + } + async createStudy() { + let item = { + json: this.json, + x00100020: this.patient.x00100020, + x00080005 : this.x00080005, + x00080020 : this.x00080020, + x00080030 : this.x00080030 ? Number(this.x00080030) : undefined, + x00080050 : this.x00080050, + x00080056 : this.x00080056, + x00080201 : this.x00080201, + x0020000D : this.x0020000D, + x00200010 : this.x00200010, + x00201206 : this.x00201206, + x00201208 : this.x00201208, + studyPath: this.studyPath + }; + let [study, created] = await StudyModel.findOrCreate({ where: { x0020000D: this.x0020000D }, - defaults: { - json: this.json, - x00100020: this.patient.x00100020, - x00080005 : this.x00080005, - x00080020 : this.x00080020, - x00080030 : this.x00080030 ? Number(this.x00080030) : undefined, - x00080050 : this.x00080050, - x00080056 : this.x00080056, - x00080201 : this.x00080201, - x0020000D : this.x0020000D, - x00200010 : this.x00200010, - x00201206 : this.x00201206, - x00201208 : this.x00201208, - studyPath: this.studyPath - } + defaults: item }); if (created) { let referringPhysicianName = await this.createReferringPhysicianName(); study.x00080090 = referringPhysicianName ? referringPhysicianName.id : undefined; await study.save(); + } else { + await StudyModel.update(item, { + where: { + x0020000D: study.dataValues.x0020000D + } + }); + await this.updateReferringPhysicianName(study); } return study; From 9b44292cd56d9288f30bd2f7c2ea08dee53717c5 Mon Sep 17 00:00:00 2001 From: chin Date: Mon, 7 Aug 2023 20:38:26 +0800 Subject: [PATCH 039/365] feat: extract 00400275 to schema (avoid json) - Series Request Attribute schema definition - Associate series and SeriesRequestAttribute (00400275) schema - One-To-One - Change JSON query to normal SQL query - Remove x00400275 in schema - Already extract to another schema --- .../QIDO-RS/service/querybuilder.js | 2 +- .../QIDO-RS/service/seriesQueryBuilder.js | 128 +++++------------- models/sql/init.js | 6 + models/sql/models/series.model.js | 3 - .../models/seriesRequestAttributes.model.js | 41 ++++++ models/sql/po/series.po.js | 53 +++++++- 6 files changed, 134 insertions(+), 99 deletions(-) create mode 100644 models/sql/models/seriesRequestAttributes.model.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index a2316107..32bd03c7 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -10,7 +10,7 @@ const sequelize = require("@models/sql/instance"); class BaseQueryBuilder { constructor(queryOptions) { this.queryOptions = queryOptions; - /** @type {import("sequelize").IncludeOptions} */ + /** @type {import("sequelize").IncludeOptions[]} */ this.includeQueries = []; this.bind = []; } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index 0c188740..1d524e0a 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -5,6 +5,7 @@ const { raccoonConfig } = require("@root/config-class"); const _ = require("lodash"); const { PersonNameModel } = require("@models/sql/models/personName.model"); const sequelize = require("@models/sql/instance"); +const { SeriesRequestAttributesModel } = require("@models/sql/models/seriesRequestAttributes.model"); class SeriesQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { @@ -97,121 +98,60 @@ class SeriesRequestAttributeSequence { /** @type {SeriesQueryBuilder} */ this.seriesQueryBuilder = seriesQueryBuilder; } - getAccessionNumber(value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { - value = this.seriesQueryBuilder.getWildCardRegexString(value); - let q = { - [Op.or]: [ - Sequelize.literal(`"x00400275" @? '$."00080050".Value[0] ? (@ like_regex "${value}")'`) - ] - }; - this.seriesQueryBuilder.query = { - ...this.seriesQueryBuilder.query, - ...q - }; - } - throw new Error("Not implemented"); + isModelIncluded() { + return this.seriesQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "SeriesRequestAttributes"); + } + getAccessionNumber(value) { + let q = this.seriesQueryBuilder.getStringQuery("00080050", value); + this.addQuery(q); } getIssuerLocalNameSpaceEntityID(value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { - value = this.seriesQueryBuilder.getWildCardRegexString(value); - let q = { - [Op.or]: [ - Sequelize.literal(`"x00400275" @? '$."00080051".Value[0]."00400031".Value[0] ? (@ like_regex "${value}")'`) - ] - }; - - this.seriesQueryBuilder.query = { - ...this.seriesQueryBuilder.query, - ...q - }; - } - - throw new Error("Not implemented"); + let q = this.seriesQueryBuilder.getStringQuery("00080051_x00400031", value); + this.addQuery(q); } getIssuerUniversalEntityID(value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { - value = this.seriesQueryBuilder.getWildCardRegexString(value); - let q = { - [Op.or]: [ - Sequelize.literal(`"x00400275" @? '$."00080051".Value[0]."00400032".Value[0] ? (@ like_regex "${value}")'`) - ] - }; - - this.seriesQueryBuilder.query = { - ...this.seriesQueryBuilder.query, - ...q - }; - } - throw new Error("Not implemented"); + let q = this.seriesQueryBuilder.getStringQuery("00080051_x00400032", value); + this.addQuery(q); } getIssuerUniversalEntityIDType(value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { - value = this.seriesQueryBuilder.getWildCardRegexString(value); - let q = { - [Op.or]: [ - Sequelize.literal(`"x00400275" @? '$."00080051".Value[0]."00400033".Value[0] ? (@ like_regex "${value}")'`) - ] - }; - - this.seriesQueryBuilder.query = { - ...this.seriesQueryBuilder.query, - ...q - }; - } - throw new Error("Not implemented"); + let q = this.seriesQueryBuilder.getStringQuery("00080051_x00400033", value); + this.addQuery(q); } getRequestingService(value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { - value = this.seriesQueryBuilder.getWildCardRegexString(value); - let q = { - [Op.or]: [ - Sequelize.literal(`"x00400275" @? '$."00321033".Value[0] ? (@ like_regex "${value}")'`) - ] - }; - - this.seriesQueryBuilder.query = { - ...this.seriesQueryBuilder.query, - ...q - }; - } - throw new Error("Not implemented"); + let q = this.seriesQueryBuilder.getStringQuery("00321033", value); + this.addQuery(q); } getRequestedProcedureID(value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { - value = this.seriesQueryBuilder.getWildCardRegexString(value); - let q = { - [Op.or]: [ - Sequelize.literal(`"x00400275" @? '$."00401001".Value[0] ? (@ like_regex "${value}")'`) - ] - }; - this.seriesQueryBuilder.query = { - ...this.seriesQueryBuilder.query, - ...q - }; - } - throw new Error("Not implemented"); + let q = this.seriesQueryBuilder.getStringQuery("00401001", value); + this.addQuery(q); } getStudyInstanceUID(value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { - value = this.seriesQueryBuilder.getWildCardRegexString(value); - let q = { - [Op.or]: [ - Sequelize.literal(`"x00400275" @? '$."0020000D".Value[0] ? (@ like_regex "${value}")'`) - ] - }; - this.seriesQueryBuilder.query = { - ...this.seriesQueryBuilder.query, + let q = this.seriesQueryBuilder.getStringQuery("0020000D", value); + this.addQuery(q); + } + + addQuery(q) { + let currentModel = this.isModelIncluded(); + if (currentModel) { + currentModel.where = { + ...currentModel.where, ...q }; + } else { + this.seriesQueryBuilder.includeQueries.push({ + model: SeriesRequestAttributesModel, + where: { + ...q + }, + attributes: [] + }); } - throw new Error("Not implemented"); } } diff --git a/models/sql/init.js b/models/sql/init.js index 3290eca0..dd19d04a 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -7,6 +7,7 @@ const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); const { raccoonConfig } = require("@root/config-class"); const sequelizeInstance = require("./instance"); +const { SeriesRequestAttributesModel } = require("./models/seriesRequestAttributes.model"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -86,6 +87,11 @@ async function init() { through: "OperatorsName" }); + SeriesModel.hasOne(SeriesRequestAttributesModel, { + foreignKey: "x0020000E", + targetKey: "x0020000E" + }); + InstanceModel.belongsTo(SeriesModel, { foreignKey: "x0020000E", targetKey: "x0020000E" diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index b35e6ede..d735c61d 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -51,9 +51,6 @@ SeriesModel.init({ "x00400245": { type: vrTypeMapping.TM }, - "x00400275": { - type: vrTypeMapping.JSON - }, "x00080031": { type: vrTypeMapping.TM }, diff --git a/models/sql/models/seriesRequestAttributes.model.js b/models/sql/models/seriesRequestAttributes.model.js new file mode 100644 index 00000000..1c1c1afc --- /dev/null +++ b/models/sql/models/seriesRequestAttributes.model.js @@ -0,0 +1,41 @@ +const { DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); +const _ = require("lodash"); + +class SeriesRequestAttributesModel extends Model {} + +SeriesRequestAttributesModel.init({ + "x0020000E": { + type: vrTypeMapping.UI, + allowNull: false + }, + "x00080050": { + type: vrTypeMapping.SH + }, + "x00080051_x00400031": { + type: vrTypeMapping.UT + }, + "x00080051_x00400032": { + type: vrTypeMapping.UT + }, + "x00080051_x00400033": { + type: vrTypeMapping.CS + }, + "x00321033": { + type: vrTypeMapping.LO + }, + "x00401001": { + type: vrTypeMapping.SH + }, + "x0020000D": { + type: vrTypeMapping.UI + } +}, { + sequelize: sequelizeInstance, + modelName: "SeriesRequestAttributes", + tableName: "SeriesRequestAttributes", + freezeTableName: true +}); + +module.exports.SeriesRequestAttributesModel = SeriesRequestAttributesModel; \ No newline at end of file diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index 41221346..19cdf1fe 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -5,6 +5,7 @@ const { SeriesModel } = require("../models/series.model"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); const sequelize = require("../instance"); +const { SeriesRequestAttributesModel } = require("../models/seriesRequestAttributes.model"); class SeriesPersistentObject { constructor(dicomJson, study) { @@ -72,6 +73,9 @@ class SeriesPersistentObject { } async updatePerformingPhysicianNames(series) { + // The value multiplicity of PerformingPhysicianName is 1-n + // We cannot sure the length of data is changed + // So, destroy all and recreate for(let personName of series.performingPhysicianName) { await personName.destroy(); } @@ -92,6 +96,47 @@ class SeriesPersistentObject { await this.addOperatorsNames(series); } + getRequestAttributesInJson() { + if (this.x00400275) { + return { + x0020000E: this.x0020000E, + x00080050: _.get(this.x00400275, "00080050.Value.0"), + x00080051_x00400031: _.get(this.x00400275, "00080051.Value.0.00400031.Value.0"), + x00080051_x00400032: _.get(this.x00400275, "00080051.Value.0.00400032.Value.0"), + x00080051_x00400033: _.get(this.x00400275, "00080051.Value.0.00400033.Value.0"), + x00321033: _.get(this.x00400275, "00321033.Value.0"), + x00401001: _.get(this.x00400275, "00401001.Value.0"), + x0020000D: _.get(this.x00400275, "0020000D.Value.0") + }; + } + return undefined; + } + + /** + * + * @param {SeriesModel} series + */ + async updateRequestAttribute(series) { + let requestAttributes = this.getRequestAttributesInJson(); + if (requestAttributes) { + if (await series.getSeriesRequestAttribute()) { + await SeriesRequestAttributesModel.update(requestAttributes, { + where: { + x0020000E: series.dataValues.x0020000E + } + }); + } else { + await series.createSeriesRequestAttribute(requestAttributes); + } + } else { + await SeriesRequestAttributesModel.destroy({ + where: { + x0020000E: series.dataValues.x0020000E + } + }); + } + } + async createSeries() { let item = { json: this.json, @@ -109,7 +154,6 @@ class SeriesPersistentObject { x00200011: this.x00200011, x00400244: this.x00400244, x00400245: this.x00400245 ? Number(this.x00400245) : undefined, - x00400275: this.x00400275, x00080031: this.x00080031 ? Number(this.x00080031) : undefined, seriesPath: this.seriesPath }; @@ -125,6 +169,11 @@ class SeriesPersistentObject { if (created) { await this.addPerformingPhysicianNames(series); await this.addOperatorsNames(series); + let requestAttributes = this.getRequestAttributesInJson(); + if (requestAttributes) { + await series.createSeriesRequestAttribute(requestAttributes); + } + await series.save(); } else { await SeriesModel.update(item, { @@ -132,6 +181,8 @@ class SeriesPersistentObject { x0020000E: series.dataValues.x0020000E } }); + await this.updateRequestAttribute(series); + let seriesWithIncludeItem = await SeriesModel.findByPk(series.dataValues.x0020000E, { include: [ { From 39a516713ced88772f6537a6bf5eaca4ef10fba5 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Tue, 8 Aug 2023 12:33:04 +0800 Subject: [PATCH 040/365] reactor: remove unused field in series po --- models/sql/models/series.model.js | 12 ++++++++++++ models/sql/po/series.po.js | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index d735c61d..a2c5465c 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -31,15 +31,27 @@ SeriesModel.init({ type: vrTypeMapping.LO }, "x0008103F": { + // Temp field for future use + // vr: SQ + // VM: 1 type: vrTypeMapping.JSON }, "x00081052": { + // Temp field for future use + // vr: SQ + // VM: 1 type: vrTypeMapping.JSON }, "x00081072": { + // Temp field for future use + // vr: SQ + // VM: 1 type: vrTypeMapping.JSON }, "x00081250": { + // Temp field for future use + // vr: SQ + // VM: 1 type: vrTypeMapping.JSON }, "x00200011": { diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index 19cdf1fe..105c7a37 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -146,9 +146,7 @@ class SeriesPersistentObject { x00080060: this.x00080060, x0008103E: this.x0008103E, x0008103F: this.x0008103F, - x00081050: this.x00081050, x00081052: this.x00081052, - x00081070: this.x00081070, x00081072: this.x00081072, x00081250: this.x00081250, x00200011: this.x00200011, From 55e1dcfe32d8ae6a089aea611cbe42294b623693 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Tue, 8 Aug 2023 12:44:57 +0800 Subject: [PATCH 041/365] fix: expose all error to client - Do not expose all error to client --- api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js | 2 +- api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js index 9ebca4e2..dcb9b3c9 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js +++ b/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js @@ -30,7 +30,7 @@ class QueryAllSeriesController extends Controller { }); this.response.end(JSON.stringify({ code: 500, - message: errorStr + message: "Server error occurred" })); } } diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js index 6fbd239a..ca2096ab 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js +++ b/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js @@ -30,7 +30,7 @@ class QueryAllStudiesController extends Controller { }); this.response.end(JSON.stringify({ code: 500, - message: errorStr + message: "Server error occurred" })); } } From 44b558efbfb16a6671583ea01025f2e56e68f4a6 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Tue, 8 Aug 2023 17:07:42 +0800 Subject: [PATCH 042/365] feat: extract 0040A043, 0040A730 to single schema - Avoid using json - create when new instance created --- models/sql/init.js | 17 +++++++ models/sql/models/dicomCode.model.js | 38 +++++++++++++++ models/sql/models/dicomContentSQ.model.js | 42 ++++++++++++++++ models/sql/models/instance.mode.js | 13 +++-- models/sql/po/instance.po.js | 59 +++++++++++++++++++++-- 5 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 models/sql/models/dicomCode.model.js create mode 100644 models/sql/models/dicomContentSQ.model.js diff --git a/models/sql/init.js b/models/sql/init.js index dd19d04a..ce528ff6 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -8,6 +8,8 @@ const { raccoonConfig } = require("@root/config-class"); const sequelizeInstance = require("./instance"); const { SeriesRequestAttributesModel } = require("./models/seriesRequestAttributes.model"); +const { DicomCodeModel } = require("./models/dicomCode.model"); +const { DicomContentSqModel } = require("./models/dicomContentSQ.model"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -96,6 +98,21 @@ async function init() { foreignKey: "x0020000E", targetKey: "x0020000E" }); + InstanceModel.hasOne(DicomCodeModel, { + foreignKey: "SOPInstanceUID", + sourceKey: "x00080018" + }); + + InstanceModel.hasOne(DicomContentSqModel, { + foreignKey: "SOPInstanceUID", + sourceKey: "x00080018" + }); + DicomContentSqModel.hasOne(DicomCodeModel, { + as: "ConceptNameCode" + }); + DicomContentSqModel.hasOne(DicomCodeModel, { + as: "ConceptCode" + }); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync(); diff --git a/models/sql/models/dicomCode.model.js b/models/sql/models/dicomCode.model.js new file mode 100644 index 00000000..6142c781 --- /dev/null +++ b/models/sql/models/dicomCode.model.js @@ -0,0 +1,38 @@ +const { Model } = require("sequelize"); +const sequelizeInstance = require("@root/models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class DicomCodeModel extends Model {}; + +DicomCodeModel.init({ + "x00080100": { + type: vrTypeMapping.SH + }, + "x00080102": { + type: vrTypeMapping.SH + }, + "x00080103": { + type: vrTypeMapping.SH + }, + "x00080104": { + type: vrTypeMapping.SH + } +}, { + sequelize: sequelizeInstance, + modelName: "DicomCode", + tableName: "DicomCode", + freezeTableName: true, + indexes: [ + { + fields: ["x00080100"] + }, + { + fields: ["x00080102"] + }, + { + fields: ["x00080103"] + } + ] +}); + +module.exports.DicomCodeModel = DicomCodeModel; \ No newline at end of file diff --git a/models/sql/models/dicomContentSQ.model.js b/models/sql/models/dicomContentSQ.model.js new file mode 100644 index 00000000..48b9c7d7 --- /dev/null +++ b/models/sql/models/dicomContentSQ.model.js @@ -0,0 +1,42 @@ +const { Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); + +class DicomContentSqModel extends Model {} + +DicomContentSqModel.init({ + "x0040A010": { + // Relationship Type + type: vrTypeMapping.CS + }, + "x0040A040": { + // Value Type + type: vrTypeMapping.CS + }, + "x0040A160": { + // Text Value + type: vrTypeMapping.UT, + validate: { + requiredIfValueType(value) { + if (!value && this.x0040A040 === "TEXT") { + throw new Error("x0040A160 is required if x0040A040 is TEXT"); + } + } + } + } +}, { + sequelize: sequelizeInstance, + modelName: "DicomContentSQ", + tableName: "DicomContentSQ", + freezeTableName: true, + indexes: [ + { + fields: ["x0040A010"] + }, + { + fields: ["x0040A160"] + } + ] +}); + +module.exports.DicomContentSqModel = DicomContentSqModel; \ No newline at end of file diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js index 187bb50d..ca6e6a60 100644 --- a/models/sql/models/instance.mode.js +++ b/models/sql/models/instance.mode.js @@ -34,10 +34,8 @@ InstanceModel.init({ "x00200013": { type: vrTypeMapping.IS }, - "x0040A043": { //SQ - type: vrTypeMapping.JSON - }, - "x0040A073": { + "x0040A073": { // VM 1, SQ + //TODO Extract to single schema type: vrTypeMapping.JSON }, "x0040A491": { @@ -46,9 +44,6 @@ InstanceModel.init({ "x0040A493": { type: vrTypeMapping.CS }, - "x0040A730": { - type: vrTypeMapping.JSON - }, "json": { type: vrTypeMapping.JSON } @@ -59,4 +54,8 @@ InstanceModel.init({ freezeTableName: true }); +InstanceModel.getDicomJson = async function(queryOptions) { + //TODO +}; + module.exports.InstanceModel = InstanceModel; diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 344946c9..26160455 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -4,6 +4,8 @@ const { PersonNameModel } = require("../models/personName.model"); const { InstanceModel } = require("../models/instance.mode"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); +const { DicomCodeModel } = require("../models/dicomCode.model"); +const { DicomContentSqModel } = require("../models/dicomContentSQ.model"); const INSTANCE_STORE_TAGS = { "00080016": true, @@ -60,13 +62,61 @@ class InstancePersistentObject { this.x00080023 = _.get(dicomJson, "00080023.Value.0", undefined); this.x00080033 = _.get(dicomJson, "00080033.Value.0", undefined); this.x00200013 = _.get(dicomJson, "00200013.Value.0", undefined); - this.x0040A043 = _.get(dicomJson, "0040A043.Value", undefined); - this.x0040A073 = _.get(dicomJson, "0040A073.Value", undefined); + this.x0040A043 = _.get(dicomJson, "0040A043.Value.0", undefined); + this.x0040A073 = _.get(dicomJson, "0040A073.Value.0", undefined); this.x0040A491 = _.get(dicomJson, "0040A491.Value.0", undefined); this.x0040A493 = _.get(dicomJson, "0040A493.Value.0", undefined); - this.x0040A730 = _.get(dicomJson, "0040A730.Value", undefined); + this.x0040A730 = _.get(dicomJson, "0040A730.Value.0", undefined); } + async createConceptNameCodeSq(instance) { + if (this.x0040A043) { + let nameCodeSq = { + "x00080100": _.get(this.x0040A043, "00080100.Value.0", undefined), + "x00080102": _.get(this.x0040A043, "00080102.Value.0", undefined), + "x00080103": _.get(this.x0040A043, "00080103.Value.0", undefined), + "x00080104": _.get(this.x0040A043, "00080104.Value.0", undefined) + }; + let dicomCode = await DicomCodeModel.create(nameCodeSq); + instance.setDicomCode(dicomCode); + await instance.save(); + } + } + + async createContentItem(instance) { + if (this.x0040A730) { + let contentItem = { + "x0040A040": _.get(this.x0040A730, "0040A040.Value.0", undefined), + "x0040A010": _.get(this.x0040A730, "0040A010.Value.0", undefined), + "x0040A160": _.get(this.x0040A730, "0040A160.Value.0", undefined) + }; + let nameCodeSq = { + "x00080100": _.get(this.x0040A730, "0040A043.Value.0.00080100.Value.0", undefined), + "x00080102": _.get(this.x0040A730, "0040A043.Value.0.00080102.Value.0", undefined), + "x00080103": _.get(this.x0040A730, "0040A043.Value.0.00080103.Value.0", undefined), + "x00080104": _.get(this.x0040A730, "0040A043.Value.0.00080104.Value.0", undefined) + }; + let conceptCodeSq = { + "x00080100": _.get(this.x0040A730, "0040A168.Value.0.00080100.Value.0", undefined), + "x00080102": _.get(this.x0040A730, "0040A168.Value.0.00080102.Value.0", undefined), + "x00080103": _.get(this.x0040A730, "0040A168.Value.0.00080103.Value.0", undefined), + "x00080104": _.get(this.x0040A730, "0040A168.Value.0.00080104.Value.0", undefined) + }; + + if (Object.values(contentItem).some(v => v)) { + let createdContentItem = await instance.createDicomContentSQ(contentItem); + + if (Object.values(nameCodeSq).some(v => v)) { + await createdContentItem.createConceptNameCode(nameCodeSq); + } + + if (Object.values(conceptCodeSq).some(v => v)) { + await createdContentItem.createConceptCode(conceptCodeSq); + } + } + + } + } async createInstance() { @@ -79,7 +129,6 @@ class InstancePersistentObject { x00080023: this.x00080023, x00080033: this.x00080033 ? Number(this.x00080033) : undefined, x00200013: this.x00200013, - x0040A043: this.x0040A043, x0040A073: this.x0040A073, x0040A491: this.x0040A491, x0040A493: this.x0040A493, @@ -98,6 +147,8 @@ class InstancePersistentObject { if (created) { // do nothing + await this.createConceptNameCodeSq(instance); + await this.createContentItem(instance); } else { await InstanceModel.update(item, { where: { From 33bb29d68270f39de8a0b3557accacd1dc19ef4d Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Tue, 8 Aug 2023 17:08:14 +0800 Subject: [PATCH 043/365] feat: query instance route --- .../controller/QIDO-RS/queryAllInstances.js | 46 +++++++++++++++++++ api-sql/dicom-web/qido-rs.route.js | 12 ++--- 2 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js new file mode 100644 index 00000000..f3899391 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js @@ -0,0 +1,46 @@ +const { + QidoRsService +} = require("./service/QIDO-RS.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class QueryAllInstancesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "QIDO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info("Query all instances"); + + try { + let qidoRsService = new QidoRsService(this.request, this.response, "instance"); + + await qidoRsService.getAndResponseDicomJson(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new QueryAllInstancesController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js index 21649cea..c40d68d3 100644 --- a/api-sql/dicom-web/qido-rs.route.js +++ b/api-sql/dicom-web/qido-rs.route.js @@ -271,12 +271,12 @@ router.get( * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" */ -// router.get( -// "/instances", validateParams(queryValidation, "query", { -// allowUnknown: true -// }), -// require("./controller/QIDO-RS/queryAllInstances") -// ); +router.get( + "/instances", validateParams(queryValidation, "query", { + allowUnknown: true + }), + require("./controller/QIDO-RS/queryAllInstances") +); /** * @openapi From 2ea6320eaabca0d639b7e1dde56a14867a66bf60 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Tue, 8 Aug 2023 17:08:22 +0800 Subject: [PATCH 044/365] feat(WIP): instance query builder --- .../QIDO-RS/service/instanceQueryBuilder.js | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js new file mode 100644 index 00000000..a4dac040 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js @@ -0,0 +1,68 @@ +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { BaseQueryBuilder } = require("./querybuilder"); + +class InstanceQueryBuilder extends BaseQueryBuilder { + constructor(queryOptions) { + super(queryOptions); + } + + /** + * + * @param {string} value + */ + getSOPClassUID(value) { + let q = this.getStringQuery(dictionary.keyword.SOPClassUID, value); + this.query = { + ...this.query, + ...q + }; + } + + /** + * + * @param {string} value + */ + getSOPInstanceUID(value) { + let q = this.getStringQuery(dictionary.keyword.SOPInstanceUID, value); + this.query = { + ...this.query, + ...q + }; + } + + /** + * + * @param {string} value + */ + getContentDate(value) { + let q = this.getDateQuery(dictionary.keyword.ContentDate, value); + this.query = { + ...this.query, + ...q + }; + } + + /** + * + * @param {string} value + */ + getContentTime(value) { + let q = this.getTimeQuery(dictionary.keyword.ContentTime, value); + this.query = { + ...this.query, + ...q + }; + } + + /** + * + * @param {string} value + */ + getInstanceNumber(value) { + let q = this.getStringQuery(dictionary.keyword.InstanceNumber, value); + this.query = { + ...this.query, + ...q + }; + } +} \ No newline at end of file From e8d6da4e32099aa7b54bb9647e0375f4f60b8910 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 8 Aug 2023 23:59:36 +0800 Subject: [PATCH 045/365] feat: update 0040A043, 0040A730 when upload exist --- models/sql/po/instance.po.js | 184 ++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 4 deletions(-) diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 26160455..271dc505 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -77,9 +77,41 @@ class InstancePersistentObject { "x00080103": _.get(this.x0040A043, "00080103.Value.0", undefined), "x00080104": _.get(this.x0040A043, "00080104.Value.0", undefined) }; - let dicomCode = await DicomCodeModel.create(nameCodeSq); - instance.setDicomCode(dicomCode); - await instance.save(); + await instance.createDicomCode(nameCodeSq); + } + } + + /** + * + * @param {InstanceModel} instance + */ + async createOrUpdateConceptNameCode(instance) { + let instanceConceptNameCode = await instance.getDicomCode(); + if (this.x0040A043) { + let nameCodeSq = { + "x00080100": _.get(this.x0040A043, "00080100.Value.0", undefined), + "x00080102": _.get(this.x0040A043, "00080102.Value.0", undefined), + "x00080103": _.get(this.x0040A043, "00080103.Value.0", undefined), + "x00080104": _.get(this.x0040A043, "00080104.Value.0", undefined) + }; + if (!instanceConceptNameCode) { + // Create + await instance.createDicomCode(nameCodeSq); + } else { + // Update + await DicomCodeModel.update(nameCodeSq, { + where: { + SOPInstanceUID: instance.dataValues.x00080018 + } + }); + } + } else { + // Delete when no concept name code + await DicomCodeModel.destroy({ + where: { + SOPInstanceUID: instance.dataValues.x00080018 + } + }); } } @@ -118,6 +150,13 @@ class InstancePersistentObject { } } + async createOrUpdateContentItem(instance) { + let contentItemPo = new ContentItemPersistentObject(this.x0040A730, instance); + // Create or Update + await contentItemPo.createOrUpdateContentItem(); + + } + async createInstance() { let item = { @@ -147,7 +186,6 @@ class InstancePersistentObject { if (created) { // do nothing - await this.createConceptNameCodeSq(instance); await this.createContentItem(instance); } else { await InstanceModel.update(item, { @@ -156,10 +194,148 @@ class InstancePersistentObject { } }); } + await this.createOrUpdateConceptNameCode(instance); + await this.createOrUpdateContentItem(instance); return instance; } } +class ContentItemPersistentObject { + /** + * + * @param {any} contentItem + * @param {InstanceModel} instance + */ + constructor(contentItem, instance) { + this.contentSq = { + "x0040A040": _.get(contentItem, "0040A040.Value.0", undefined), + "x0040A010": _.get(contentItem, "0040A010.Value.0", undefined), + "x0040A160": _.get(contentItem, "0040A160.Value.0", undefined) + }; + this.nameCodeSq = { + "x00080100": _.get(contentItem, "0040A043.Value.0.00080100.Value.0", undefined), + "x00080102": _.get(contentItem, "0040A043.Value.0.00080102.Value.0", undefined), + "x00080103": _.get(contentItem, "0040A043.Value.0.00080103.Value.0", undefined), + "x00080104": _.get(contentItem, "0040A043.Value.0.00080104.Value.0", undefined) + }; + this.conceptCodeSq = { + "x00080100": _.get(contentItem, "0040A168.Value.0.00080100.Value.0", undefined), + "x00080102": _.get(contentItem, "0040A168.Value.0.00080102.Value.0", undefined), + "x00080103": _.get(contentItem, "0040A168.Value.0.00080103.Value.0", undefined), + "x00080104": _.get(contentItem, "0040A168.Value.0.00080104.Value.0", undefined) + }; + this.instance = instance; + } + + async getExistContentItem() { + return await this.instance.getDicomContentSQ(); + } + + async createOrUpdateContentItem () { + if (!await this.getExistContentItem()) { + // Create + await this.createDicomContentSq(); + await this.createConceptNameCodeInContentItem(); + await this.createConceptCodeInContentItem(); + } else { + // Update + await this.updateConceptNameCodeInContentItem(); + await this.updateConceptCodeInContentItem(); + await this.updateDicomContentSq(); + } + } + + async createDicomContentSq() { + if (Object.values(this.contentSq).some(v => v)) { + await this.instance.createDicomContentSQ(this.contentSq); + } + } + + async updateDicomContentSq() { + if (Object.values(this.contentSq).some(v => v)) { + // Update value + await DicomContentSqModel.update(this.contentSq, { + where: { + SOPInstanceUID: this.instance.dataValues.x00080018 + } + }); + } else { + // Remove item because of given item does not exist + await DicomContentSqModel.destroy({ + where: { + SOPInstanceUID: this.instance.dataValues.x00080018 + } + }); + } + } + + async createConceptNameCodeInContentItem() { + if (Object.values(this.nameCodeSq).some(v => v)) { + if (this.createdContentSq) + await this.createdContentSq.createConceptNameCode(this.nameCodeSq); + } + } + + async updateConceptNameCodeInContentItem() { + let contentItemInInstance = await this.getExistContentItem(); + if (Object.values(this.nameCodeSq).some(v => v)) { + if (contentItemInInstance) { + let nameCode = await contentItemInInstance.getConceptNameCode(); + if (nameCode) { + await DicomCodeModel.update(this.nameCodeSq, { + where: { + ConceptNameCodeId: contentItemInInstance.dataValues.id + } + }); + } else { + await contentItemInInstance.createConceptNameCode(this.nameCodeSq); + } + } + } else { + if (contentItemInInstance) { + await DicomCodeModel.destroy({ + where: { + ConceptNameCodeId: contentItemInInstance.dataValues.id + } + }); + } + } + } + + async createConceptCodeInContentItem() { + if (Object.values(this.conceptCodeSq).some(v => v)) { + if (this.createdContentSq) + await this.createdContentSq.createConceptCode(this.conceptCodeSq); + } + } + + async updateConceptCodeInContentItem() { + let contentItemInInstance = await this.getExistContentItem(); + if (Object.values(this.nameCodeSq).some(v => v)) { + if (contentItemInInstance) { + let conceptCode = await contentItemInInstance.getConceptCode(); + if (conceptCode) { + await DicomCodeModel.update(this.conceptCodeSq, { + where: { + ConceptCodeId: contentItemInInstance.dataValues.id + } + }); + } else { + await contentItemInInstance.createConceptCode(this.nameCodeSq); + } + } + } else { + if (contentItemInInstance) { + await DicomCodeModel.destroy({ + where: { + ConceptCodeId: contentItemInInstance.dataValues.id + } + }); + } + } + } +} + module.exports.InstancePersistentObject = InstancePersistentObject; \ No newline at end of file From 7d3f83b61b7e948425630036c3dbdb4c61100c91 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 9 Aug 2023 16:00:22 +0800 Subject: [PATCH 046/365] fix: incorrect type of x00080104 in code schema --- models/sql/models/dicomCode.model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/sql/models/dicomCode.model.js b/models/sql/models/dicomCode.model.js index 6142c781..4efb9c8e 100644 --- a/models/sql/models/dicomCode.model.js +++ b/models/sql/models/dicomCode.model.js @@ -15,7 +15,7 @@ DicomCodeModel.init({ type: vrTypeMapping.SH }, "x00080104": { - type: vrTypeMapping.SH + type: vrTypeMapping.LO } }, { sequelize: sequelizeInstance, From 54bac253b58a4f758d054a6b216f5837b2540a18 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 9 Aug 2023 16:01:44 +0800 Subject: [PATCH 047/365] feat: extract x0040A073 to single schema --- models/sql/init.js | 13 +++++++ models/sql/models/instance.mode.js | 4 --- .../sql/models/verifyingObserverSQ.model.js | 34 +++++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 models/sql/models/verifyingObserverSQ.model.js diff --git a/models/sql/init.js b/models/sql/init.js index ce528ff6..e9b957b3 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -10,6 +10,7 @@ const sequelizeInstance = require("./instance"); const { SeriesRequestAttributesModel } = require("./models/seriesRequestAttributes.model"); const { DicomCodeModel } = require("./models/dicomCode.model"); const { DicomContentSqModel } = require("./models/dicomContentSQ.model"); +const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -98,11 +99,23 @@ async function init() { foreignKey: "x0020000E", targetKey: "x0020000E" }); + InstanceModel.hasOne(DicomCodeModel, { foreignKey: "SOPInstanceUID", sourceKey: "x00080018" }); + InstanceModel.hasOne(VerifyIngObserverSqModel, { + foreignKey: "SOPInstanceUID", + sourceKey: "x00080018" + }); + VerifyIngObserverSqModel.hasOne(DicomCodeModel, { + foreignKey: "x0040A088" + }); + VerifyIngObserverSqModel.belongsTo(PersonNameModel, { + foreignKey: "x0040A075" + }); + InstanceModel.hasOne(DicomContentSqModel, { foreignKey: "SOPInstanceUID", sourceKey: "x00080018" diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js index ca6e6a60..57c1049d 100644 --- a/models/sql/models/instance.mode.js +++ b/models/sql/models/instance.mode.js @@ -34,10 +34,6 @@ InstanceModel.init({ "x00200013": { type: vrTypeMapping.IS }, - "x0040A073": { // VM 1, SQ - //TODO Extract to single schema - type: vrTypeMapping.JSON - }, "x0040A491": { type: vrTypeMapping.CS }, diff --git a/models/sql/models/verifyingObserverSQ.model.js b/models/sql/models/verifyingObserverSQ.model.js new file mode 100644 index 00000000..c6e29124 --- /dev/null +++ b/models/sql/models/verifyingObserverSQ.model.js @@ -0,0 +1,34 @@ +/** + * This Tag(Verifying Observer SQ, 0040,A073) only used in SR Document + */ +const { Model, DataTypes } = require("sequelize"); +const { vrTypeMapping } = require("../vrTypeMapping"); +const sequelizeInstance = require("../instance"); + +class VerifyIngObserverSqModel extends Model {} + +VerifyIngObserverSqModel.init({ + "x0040A027": { + // Verifying Organization + type: vrTypeMapping.LO + }, + "x0040A030": { + // Verification DateTime + type: vrTypeMapping.DT + }, + "x0040A075": { + // Verifying Observer Name + type: vrTypeMapping.PN + }, + "x0040A088": { + // Verifying Observer Identification Code Sequence (foreign key) + type: DataTypes.INTEGER + } +}, { + sequelize: sequelizeInstance, + modelName: "VerifyingObserverSQ", + tableName: "VerifyingObserverSQ", + freezeTableName: true +}); + +module.exports.VerifyIngObserverSqModel = VerifyIngObserverSqModel; \ No newline at end of file From d2f1ccc6c2e9eaaf0a0cbd4ea675b4a505dbd35e Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 9 Aug 2023 16:01:49 +0800 Subject: [PATCH 048/365] feat: create or update x0040A073 when store --- models/sql/po/instance.po.js | 178 ++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 4 deletions(-) diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 271dc505..71be7139 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -6,6 +6,7 @@ const { InstanceModel } = require("../models/instance.mode"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); const { DicomCodeModel } = require("../models/dicomCode.model"); const { DicomContentSqModel } = require("../models/dicomContentSQ.model"); +const { VerifyIngObserverSqModel } = require("../models/verifyingObserverSQ.model"); const INSTANCE_STORE_TAGS = { "00080016": true, @@ -141,7 +142,7 @@ class InstancePersistentObject { if (Object.values(nameCodeSq).some(v => v)) { await createdContentItem.createConceptNameCode(nameCodeSq); } - + if (Object.values(conceptCodeSq).some(v => v)) { await createdContentItem.createConceptCode(conceptCodeSq); } @@ -154,9 +155,15 @@ class InstancePersistentObject { let contentItemPo = new ContentItemPersistentObject(this.x0040A730, instance); // Create or Update await contentItemPo.createOrUpdateContentItem(); - + + } + + async createOrUpdateVerifyingObserverSq(instance) { + let verifyingObserverSqPo = new VerifyingObserverSqPersistentObject(this.x0040A073, this.x0040A493, instance); + await verifyingObserverSqPo.createOrUpdate(); } + async createInstance() { let item = { @@ -196,6 +203,7 @@ class InstancePersistentObject { } await this.createOrUpdateConceptNameCode(instance); await this.createOrUpdateContentItem(instance); + await this.createOrUpdateVerifyingObserverSq(instance); return instance; } @@ -228,12 +236,12 @@ class ContentItemPersistentObject { }; this.instance = instance; } - + async getExistContentItem() { return await this.instance.getDicomContentSQ(); } - async createOrUpdateContentItem () { + async createOrUpdateContentItem() { if (!await this.getExistContentItem()) { // Create await this.createDicomContentSq(); @@ -338,4 +346,166 @@ class ContentItemPersistentObject { } } +class VerifyingObserverSqPersistentObject { + constructor(verifyingObserverSq, verificationFlag, instance) { + if (verificationFlag === "VERIFIED" && !verifyingObserverSq) { + throw new Error("Verifying observer is required when Verification Flag (0040,A493) is VERIFIED"); + } + this.verifyingObserverSq = verifyingObserverSq; + this.instance = instance; + } + + async getExistItem() { + return await this.instance.getVerifyingObserverSQ(); + } + + async createOrUpdate() { + let verifyingObserverSq = { + "x0040A027": _.get(this.verifyingObserverSq, "0040A027.Value.0", undefined), + "x0040A030": _.get(this.verifyingObserverSq, "0040A030.Value.0", undefined) + }; + + + verifyingObserverSq.x0040A030 = verifyingObserverSq.x0040A030 ? + moment(verifyingObserverSq.x0040A030, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString() : + undefined; + + if (!await this.getExistItem()) { + // create + let name = await this.createName(); + let personNameId = name ? name.dataValues.id : undefined; + + let identificationCode = await this.createIdentificationCode(); + let identificationCodeId = identificationCode ? identificationCode.dataValues.id : undefined; + + await this.instance.createVerifyingObserverSQ({ + ...verifyingObserverSq, + x0040A088: identificationCodeId, + x0040A075: personNameId + }); + } else { + let name = await this.updateName(); + let personNameId = name ? name.dataValues.id : undefined; + + let identificationCode = await this.updateIdentificationCode(); + let identificationCodeId = identificationCode ? identificationCode.dataValues.id : undefined; + + await VerifyIngObserverSqModel.update({ + ...verifyingObserverSq, + x0040A088: identificationCodeId, + x0040A075: personNameId + }, { + where: { + SOPInstanceUID: this.instance.dataValues.x00080018 + } + }); + } + + } + + /** + * create Verifying Observer Name + */ + async createName() { + let nameItem = _.get(this.verifyingObserverSq, "0040A075.Value.0"); + if (nameItem && Object.values(nameItem).some(v => v)) { + return await PersonNameModel.create({ + alphabetic: _.get(nameItem, "Alphabetic", undefined), + ideographic: _.get(nameItem, "Ideographic", undefined), + phonetic: _.get(nameItem, "Phonetic", undefined) + }); + } + return undefined; + } + + async updateName() { + let verifyingObserverSq = await this.getExistItem(); + let name = await verifyingObserverSq.getPersonName(); + let nameItem = _.get(this.verifyingObserverSq, "0040A075.Value.0"); + + if (name) { + if (nameItem && Object.values(nameItem).some(v => v)) { + await PersonNameModel.update({ + alphabetic: _.get(nameItem, "Alphabetic", undefined), + ideographic: _.get(nameItem, "Ideographic", undefined), + phonetic: _.get(nameItem, "Phonetic", undefined) + }, { + where: { + id: name.dataValues.id + } + }); + return name; + } + + await VerifyIngObserverSqModel.update({ + x0040A075: null + }, { + where: { + SOPInstanceUID: this.instance.dataValues.x00080018 + } + }); + await PersonNameModel.destroy({ + where: { + id: name.dataValues.id + } + }); + + } else { + return await this.createName(); + } + return undefined; + } + + async createIdentificationCode() { + let codeItem = _.get(this.verifyingObserverSq, "0040A088.Value.0"); + if (codeItem && Object.values(codeItem).some(v => v)) { + return await DicomCodeModel.create({ + "x00080100": _.get(codeItem, "00080100.Value.0", undefined), + "x00080102": _.get(codeItem, "00080102.Value.0", undefined), + "x00080103": _.get(codeItem, "00080103.Value.0", undefined), + "x00080104": _.get(codeItem, "00080104.Value.0", undefined) + }); + } + return undefined; + } + + async updateIdentificationCode() { + let verifyingObserverSq = await this.getExistItem(); + let code = await verifyingObserverSq.getDicomCode(); + let newCodeItem = _.get(this.verifyingObserverSq, "0040A088.Value.0"); + + if (code) { + if (newCodeItem && Object.values(newCodeItem).some(v => v)) { + await DicomCodeModel.update({ + "x00080100": _.get(newCodeItem, "00080100.Value.0", undefined), + "x00080102": _.get(newCodeItem, "00080102.Value.0", undefined), + "x00080103": _.get(newCodeItem, "00080103.Value.0", undefined), + "x00080104": _.get(newCodeItem, "00080104.Value.0", undefined) + }, { + where: { + id: code.dataValues.id + } + }); + return code; + } + + // delete when empty code + await VerifyIngObserverSqModel.update({ + x0040A088: null + }, { + where: { + SOPInstanceUID: this.instance.dataValues.x00080018 + } + }); + await DicomCodeModel.destroy({ + where: { + id: code.dataValues.id + } + }); + } else { + return await this.createIdentificationCode(); + } + } +} + module.exports.InstancePersistentObject = InstancePersistentObject; \ No newline at end of file From bbfe1d898c8077e9c32fa60fc7ddc8c48bf9f6e9 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 9 Aug 2023 17:28:52 +0800 Subject: [PATCH 049/365] feat: instance query builder - related: 2ea6320eaabca0d639b7e1dde56a14867a66bf60 --- .../controller/QIDO-RS/queryAllInstances.js | 2 +- .../QIDO-RS/service/QIDO-RS.service.js | 2 + .../QIDO-RS/service/instanceQueryBuilder.js | 311 +++++++++++++++++- models/sql/models/instance.mode.js | 27 +- 4 files changed, 339 insertions(+), 3 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js index f3899391..cedf9169 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js +++ b/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js @@ -1,5 +1,5 @@ const { - QidoRsService + SqlQidoRsService: QidoRsService } = require("./service/QIDO-RS.service"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { Controller } = require("@root/api/controller.class"); diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 99a29d9b..6954a337 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -10,6 +10,7 @@ const { } = require("@error/dicom-web-service"); const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); +const { InstanceModel } = require("@models/sql/models/instance.mode"); class SqlQidoRsService extends QidoRsService { @@ -94,6 +95,7 @@ class QidoDicomJsonFactory { }, "instance": async () => { // return await getInstanceDicomJson(queryOptions); + return await InstanceModel.getDicomJson(queryOptions); } }; } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js index a4dac040..6b127106 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js @@ -1,9 +1,37 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { BaseQueryBuilder } = require("./querybuilder"); +const { DicomCodeModel } = require("@models/sql/models/dicomCode.model"); +const { DicomContentSqModel } = require("@models/sql/models/dicomContentSQ.model"); +const { VerifyIngObserverSqModel } = require("@models/sql/models/verifyingObserverSQ.model"); +const { PersonNameModel } = require("@models/sql/models/personName.model"); class InstanceQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { super(queryOptions); + + let conceptNameCodeQueryBuilder = new ConceptNameCodeSqQueryBuilder(this); + this["0040A043.00080100"] = ConceptNameCodeSqQueryBuilder.prototype.getCodeValue.bind(conceptNameCodeQueryBuilder); + this["0040A043.00080102"] = ConceptNameCodeSqQueryBuilder.prototype.getCodingSchemeDesignator.bind(conceptNameCodeQueryBuilder); + this["0040A043.00080103"] = ConceptNameCodeSqQueryBuilder.prototype.getCodingSchemeVersion.bind(conceptNameCodeQueryBuilder); + this["0040A043.00080104"] = ConceptNameCodeSqQueryBuilder.prototype.getCodeMeaning.bind(conceptNameCodeQueryBuilder); + + let contentQueryBuilder = new ContentSqQueryBuilder(this); + this["0040A730.0040A040"] = ContentSqQueryBuilder.prototype.getValueType.bind(contentQueryBuilder); + this["0040A730.0040A010"] = ContentSqQueryBuilder.prototype.getRelationshipType.bind(contentQueryBuilder); + this["0040A730.0040A160"] = ContentSqQueryBuilder.prototype.getValueType.bind(contentQueryBuilder); + this["0040A730.0040A043.00080100"] = ContentSqQueryBuilder.prototype.getConceptNameCodeValue.bind(contentQueryBuilder); + this["0040A730.0040A043.00080102"] = ContentSqQueryBuilder.prototype.getConceptNameCodingSchemeDesignator.bind(contentQueryBuilder); + this["0040A730.0040A043.00080103"] = ContentSqQueryBuilder.prototype.getConceptNameCodingSchemeVersion.bind(contentQueryBuilder); + this["0040A730.0040A043.00080104"] = ContentSqQueryBuilder.prototype.getConceptNameCodeMeaning.bind(contentQueryBuilder); + this["0040A730.0040A168.00080100"] = ContentSqQueryBuilder.prototype.getConceptCodeValue.bind(contentQueryBuilder); + this["0040A730.0040A168.00080102"] = ContentSqQueryBuilder.prototype.getConceptCodingSchemeDesignator.bind(contentQueryBuilder); + this["0040A730.0040A168.00080103"] = ContentSqQueryBuilder.prototype.getConceptCodingSchemeVersion.bind(contentQueryBuilder); + this["0040A730.0040A168.00080104"] = ContentSqQueryBuilder.prototype.getConceptCodeMeaning.bind(contentQueryBuilder); + + let verifyingObserverQueryBuilder = new VerifyingObserverQueryBuilder(this); + this["0040A073.0040A075"] = VerifyingObserverQueryBuilder.prototype.getName.bind(verifyingObserverQueryBuilder); + this["0040A073.0040A030"] = VerifyingObserverQueryBuilder.prototype.getDateTime.bind(verifyingObserverQueryBuilder); + this["0040A073.0040A027"] = VerifyingObserverQueryBuilder.prototype.getOrganization.bind(verifyingObserverQueryBuilder); } /** @@ -65,4 +93,285 @@ class InstanceQueryBuilder extends BaseQueryBuilder { ...q }; } -} \ No newline at end of file +} + +class ConceptNameCodeSqQueryBuilder { + constructor(instanceQueryBuilder) { + /** @type {InstanceQueryBuilder} */ + this.instanceQueryBuilder = instanceQueryBuilder; + } + + isModelIncluded() { + return this.instanceQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "ConceptNameCodeSQ"); + } + + getCodeValue(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeValue, value); + this.addQuery(q); + } + + getCodingSchemeDesignator(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeDesignator, value); + this.addQuery(q); + } + + getCodingSchemeVersion(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion ,value); + this.addQuery(q); + } + + getCodeMeaning(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeMeaning, value); + this.addQuery(q); + } + + addQuery(q) { + let currentModel = this.isModelIncluded(); + if (currentModel) { + currentModel.where = { + ...currentModel.where, + ...q + }; + } else { + this.instanceQueryBuilder.includeQueries.push({ + model: DicomCodeModel, + where: { + ...q + }, + attributes: [] + }); + } + } +} + +class ContentSqQueryBuilder { + constructor(instanceQueryBuilder) { + /** @type {InstanceQueryBuilder} */ + this.instanceQueryBuilder = instanceQueryBuilder; + this.conceptNameCodeInclude = undefined; + } + + isModelIncluded() { + return this.instanceQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "DicomContentSQ"); + } + + isConceptNameCodeInclude() { + let currentModel = this.isModelIncluded(); + if (currentModel && currentModel.include) { + return currentModel.include.find( v => v.model.getTableName() === "ConceptNameCode"); + } + return false; + } + + isConceptCodeInclude() { + let currentModel = this.isModelIncluded(); + if (currentModel && currentModel.include) { + return currentModel.include.find( v=> v.model.getTableName() === "ConceptCode"); + } + return false; + } + + getValueType(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.ValueType, value); + this.addQuery(q); + } + + getTextValue(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.TextValue, value); + this.addQuery(q); + } + + getRelationshipType(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.RelationshipType, value); + this.addQuery(q); + } + + getConceptNameCodeValue(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeValue, value); + this.addConceptNameCodeQuery(q); + } + + getConceptNameCodingSchemeDesignator(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeDesignator, value); + this.addConceptNameCodeQuery(q); + } + + getConceptNameCodingSchemeVersion(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion, value); + this.addConceptNameCodeQuery(q); + } + + getConceptNameCodeMeaning(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeMeaning, value); + this.addConceptNameCodeQuery(q); + } + + getConceptCodeValue (value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeValue, value); + this.addConceptCodeQuery(q); + } + + getConceptCodingSchemeDesignator(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeDesignator, value); + this.addConceptCodeQuery(q); + } + + getConceptCodingSchemeVersion(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion, value); + this.addConceptCodeQuery(q); + } + + getConceptCodeMeaning(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeMeaning, value); + this.addConceptCodeQuery(q); + } + + addQuery(q) { + let currentModel = this.isModelIncluded(); + if (currentModel) { + currentModel.where = { + ...currentModel.where, + ...q + }; + } else { + this.instanceQueryBuilder.includeQueries.push({ + model: DicomContentSqModel, + where: { + ...q + }, + attributes: [] + }); + } + } + + addConceptNameCodeQuery(q) { + if (!this.conceptNameCodeInclude) { + this.conceptNameCodeInclude = { + model: DicomCodeModel, + as: "ConceptNameCode", + where: { + ...q + }, + attributes: [] + }; + } else { + this.conceptNameCodeInclude.where = { + ...this.conceptNameCodeInclude.where, + ...q + }; + } + + let conceptNameIncluded = this.isConceptNameCodeInclude(); + + if (conceptNameIncluded) { + conceptNameIncluded = this.conceptNameCodeInclude; + } else { + this.addQuery({}); + let currentModel = this.isModelIncluded(); + currentModel.include = currentModel.include ? currentModel.include : []; + conceptNameIncluded = this.isConceptNameCodeInclude(); + currentModel.include.push(this.conceptNameCodeInclude); + } + } + + addConceptCodeQuery(q) { + if (!this.conceptCodeInclude) { + this.conceptCodeInclude = { + model: DicomCodeModel, + as: "ConceptCode", + where: { + ...q + }, + attributes: [] + }; + } else { + this.conceptCodeInclude.where = { + ...this.conceptCodeInclude.where, + ...q + }; + } + + let conceptCodeIncluded = this.isConceptCodeInclude(); + + if (conceptCodeIncluded) { + conceptCodeIncluded = this.conceptCodeInclude; + } else { + this.addQuery({}); + let currentModel = this.isModelIncluded(); + currentModel.include = currentModel.include ? currentModel.include : []; + conceptCodeIncluded = this.isConceptCodeInclude(); + currentModel.include.push(this.conceptCodeInclude); + } + } +} + +class VerifyingObserverQueryBuilder { + constructor(instanceQueryBuilder) { + /** @type {InstanceQueryBuilder} */ + this.instanceQueryBuilder = instanceQueryBuilder; + } + + isModelIncluded() { + return this.instanceQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "VerifyingObserverSQ"); + } + + isPersonNameIncluded() { + let currentModel = this.isModelIncluded(); + if (currentModel && currentModel.include) { + return currentModel.include.find( v => v.model.getTableName() === "PersonName"); + } + return false; + } + + getName(value) { + let q = this.instanceQueryBuilder.getPersonNameQuery(dictionary.keyword.VerifyingObserverName, value); + this.addQuery({}); + let currentModel = this.isModelIncluded(); + currentModel.include = currentModel.include ? currentModel.include : []; + let personNameIncluded = this.isPersonNameIncluded(); + if (personNameIncluded) { + personNameIncluded.where = { + ...personNameIncluded.where, + ...q.query + }; + } else { + currentModel.include.push({ + model: PersonNameModel, + where: { + ...q.query + }, + attributes: [] + }); + } + } + + getDateTime(value) { + let q = this.instanceQueryBuilder.getDateQuery(dictionary.keyword.VerificationDateTime, value); + this.addQuery(q); + } + + getOrganization(value) { + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.VerifyingOrganization, value); + this.addQuery(q); + } + + addQuery(q) { + let currentModel = this.isModelIncluded(); + if (currentModel) { + currentModel.where = { + ...currentModel.where, + ...q + }; + } else { + this.instanceQueryBuilder.includeQueries.push({ + model: VerifyIngObserverSqModel, + where: { + ...q + }, + attributes: [] + }); + } + } +} + +module.exports.InstanceQueryBuilder = InstanceQueryBuilder; \ No newline at end of file diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js index 57c1049d..e3e10828 100644 --- a/models/sql/models/instance.mode.js +++ b/models/sql/models/instance.mode.js @@ -1,6 +1,9 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); +const _ = require("lodash"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); +const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); class InstanceModel extends Model { }; @@ -51,7 +54,29 @@ InstanceModel.init({ }); InstanceModel.getDicomJson = async function(queryOptions) { - //TODO + let queryBuilder = new InstanceQueryBuilder(queryOptions); + let q = queryBuilder.build(); + let seriesArray = await InstanceModel.findAll({ + ...q, + attributes: ["json", "x0020000D", "x0020000E", "x00080018"], + limit: queryOptions.limit, + offset: queryOptions.skip + }); + + return await Promise.all(seriesArray.map(async series => { + let { json } = series.toJSON(); + // Set Retrieve URL + let studyInstanceUID = _.get(json, "0020000D.Value.0"); + let seriesInstanceUID = _.get(json, "0020000E.Value.0"); + let sopInstanceUID = _.get(json, "00080018.Value.0"); + _.set(json, dictionary.keyword.RetrieveURL, { + vr: dictionary.tagVR[dictionary.keyword.RetrieveURL].vr, + Value: [ + `${queryOptions.retrieveBaseUrl}/${studyInstanceUID}/series/${seriesInstanceUID}/instances/${sopInstanceUID}` + ] + }); + return json; + })); }; module.exports.InstanceModel = InstanceModel; From 51c7a0b3a60c97e64cfd5b7080b704391db63cc2 Mon Sep 17 00:00:00 2001 From: chin Date: Wed, 9 Aug 2023 23:05:24 +0800 Subject: [PATCH 050/365] feat: support hierarchy level query - That is, in series level, we shall query study and series - In instance shall query study, series and instance --- .../QIDO-RS/service/instanceQueryBuilder.js | 13 ++++++++++++- .../QIDO-RS/service/seriesQueryBuilder.js | 7 +++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js index 6b127106..cda89758 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js @@ -1,9 +1,11 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { BaseQueryBuilder } = require("./querybuilder"); +const { BaseQueryBuilder, StudyQueryBuilder } = require("./querybuilder"); const { DicomCodeModel } = require("@models/sql/models/dicomCode.model"); const { DicomContentSqModel } = require("@models/sql/models/dicomContentSQ.model"); const { VerifyIngObserverSqModel } = require("@models/sql/models/verifyingObserverSQ.model"); const { PersonNameModel } = require("@models/sql/models/personName.model"); +const sequelize = require("@models/sql/instance"); +const { SeriesQueryBuilder } = require("./seriesQueryBuilder"); class InstanceQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { @@ -32,6 +34,15 @@ class InstanceQueryBuilder extends BaseQueryBuilder { this["0040A073.0040A075"] = VerifyingObserverQueryBuilder.prototype.getName.bind(verifyingObserverQueryBuilder); this["0040A073.0040A030"] = VerifyingObserverQueryBuilder.prototype.getDateTime.bind(verifyingObserverQueryBuilder); this["0040A073.0040A027"] = VerifyingObserverQueryBuilder.prototype.getOrganization.bind(verifyingObserverQueryBuilder); + + + let seriesQueryBuilder = new SeriesQueryBuilder(queryOptions); + let seriesQuery = seriesQueryBuilder.build(); + this.includeQueries.push({ + model: sequelize.model("Series"), + attributes: ["x0020000E"], + ...seriesQuery + }); } /** diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index 1d524e0a..33cbe5a2 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -1,5 +1,5 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { BaseQueryBuilder } = require("./querybuilder"); +const { BaseQueryBuilder, StudyQueryBuilder } = require("./querybuilder"); const { Op, Sequelize } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); const _ = require("lodash"); @@ -19,9 +19,12 @@ class SeriesQueryBuilder extends BaseQueryBuilder { this["00400275.00401001"] = SeriesRequestAttributeSequence.prototype.getRequestedProcedureID.bind(seriesRequestAttributeSequence); this["00400275.0020000D"] = SeriesRequestAttributeSequence.prototype.getStudyInstanceUID.bind(seriesRequestAttributeSequence); + let studyQueryBuilder = new StudyQueryBuilder(queryOptions); + let studyQuery = studyQueryBuilder.build(); this.includeQueries.push({ model: sequelize.model("Study"), - attributes: [] + attributes: ["x0020000D"], + ...studyQuery }); } getSeriesDate(value) { From 76e02e08ecad430c89b53d3ba438c57da2640827 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 13:56:19 +0800 Subject: [PATCH 051/365] feat: query builder add uid query when params present --- .../controller/QIDO-RS/service/instanceQueryBuilder.js | 8 ++++++++ .../dicom-web/controller/QIDO-RS/service/querybuilder.js | 7 +++++++ .../controller/QIDO-RS/service/seriesQueryBuilder.js | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js index cda89758..559f3b6c 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js @@ -1,3 +1,4 @@ +const _ = require("lodash"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { BaseQueryBuilder, StudyQueryBuilder } = require("./querybuilder"); const { DicomCodeModel } = require("@models/sql/models/dicomCode.model"); @@ -43,6 +44,13 @@ class InstanceQueryBuilder extends BaseQueryBuilder { attributes: ["x0020000E"], ...seriesQuery }); + + let instanceUidInParams = _.get(this.queryOptions.requestParams, "instanceUID"); + if (instanceUidInParams) { + this.query = { + x00080018: instanceUidInParams + }; + } } /** diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 32bd03c7..8d57afd6 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -288,6 +288,13 @@ class StudyQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { super(queryOptions); this.query = {}; + + let studyInstanceUidInParams = _.get(this.queryOptions.requestParams, "studyUID"); + if (studyInstanceUidInParams) { + this.query = { + x0020000D: studyInstanceUidInParams + }; + } } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index 33cbe5a2..d6694996 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -26,6 +26,13 @@ class SeriesQueryBuilder extends BaseQueryBuilder { attributes: ["x0020000D"], ...studyQuery }); + + let seriesInstanceUidInParams = _.get(this.queryOptions.requestParams, "seriesUID"); + if (seriesInstanceUidInParams) { + this.query = { + x0020000E: seriesInstanceUidInParams + }; + } } getSeriesDate(value) { let q = this.getDateQuery(dictionary.keyword.SeriesDate, value); From 2f066aab1e90f3e295b6dee0ea12315a47402e4c Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 13:58:13 +0800 Subject: [PATCH 052/365] feat: search instances in a study by study uid API --- .../QIDO-RS/queryStudies-Instances.js | 47 +++++++++++++++++++ api-sql/dicom-web/qido-rs.route.js | 12 ++--- 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js new file mode 100644 index 00000000..682913a4 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js @@ -0,0 +1,47 @@ +const { + SqlQidoRsService: QidoRsService +} = require("./service/QIDO-RS.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class QueryInstancesOfStudiesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "QIDO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info(`Query instances in study, Study UID: ${this.request.params.studyUID}`); + + try { + + let qidoRsService = new QidoRsService(this.request, this.response, "instance"); + + await qidoRsService.getAndResponseDicomJson(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new QueryInstancesOfStudiesController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js index c40d68d3..64621814 100644 --- a/api-sql/dicom-web/qido-rs.route.js +++ b/api-sql/dicom-web/qido-rs.route.js @@ -151,12 +151,12 @@ router.get("/studies", validateParams(queryValidation, "query", { * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" */ -// router.get( -// "/studies/:studyUID/instances", validateParams(queryValidation, "query", { -// allowUnknown: true -// }), -// require("./controller/QIDO-RS/queryStudies-Instances") -// ); +router.get( + "/studies/:studyUID/instances", validateParams(queryValidation, "query", { + allowUnknown: true + }), + require("./controller/QIDO-RS/queryStudies-Instances") +); /** * @openapi From 7672b4b4420f6b7bb7be7a46af14fab9d76bf3a5 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 14:19:56 +0800 Subject: [PATCH 053/365] feat: search series in a study by study uid API --- .../controller/QIDO-RS/queryStudies-Series.js | 47 +++++++++++++++++++ api-sql/dicom-web/qido-rs.route.js | 12 ++--- 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js new file mode 100644 index 00000000..0d6e8215 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js @@ -0,0 +1,47 @@ +const { + SqlQidoRsService: QidoRsService +} = require("./service/QIDO-RS.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class QuerySeriesOfStudiesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "QIDO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info(`Query series Level, Study UID: ${this.request.params.studyUID}`); + + try { + + let qidoRsService = new QidoRsService(this.request, this.response, "series"); + + await qidoRsService.getAndResponseDicomJson(); + + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } +} +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new QuerySeriesOfStudiesController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js index 64621814..5fbd2399 100644 --- a/api-sql/dicom-web/qido-rs.route.js +++ b/api-sql/dicom-web/qido-rs.route.js @@ -110,12 +110,12 @@ router.get("/studies", validateParams(queryValidation, "query", { * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" */ -// router.get( -// "/studies/:studyUID/series", validateParams(queryValidation, "query", { -// allowUnknown: true -// }), -// require("./controller/QIDO-RS/queryStudies-Series") -// ); +router.get( + "/studies/:studyUID/series", validateParams(queryValidation, "query", { + allowUnknown: true + }), + require("./controller/QIDO-RS/queryStudies-Series") +); /** * @openapi From be576b681a036d5f66f11becdf57261af871baa7 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 14:22:58 +0800 Subject: [PATCH 054/365] feat: search instances in a series by uids API --- .../QIDO-RS/queryStudies-Series-Instance.js | 49 +++++++++++++++++++ api-sql/dicom-web/qido-rs.route.js | 12 ++--- 2 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js new file mode 100644 index 00000000..005a986d --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js @@ -0,0 +1,49 @@ +const _ = require("lodash"); +const { + SqlQidoRsService: QidoRsService +} = require("./service/QIDO-RS.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class QueryInstancesOfSeriesOfStudiesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "QIDO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info(`Query instance Level, Study UID: ${this.request.params.studyUID}, Series UID: ${this.request.params.seriesUID}`); + + try { + + let qidoRsService = new QidoRsService(this.request, this.response, "instance"); + + await qidoRsService.getAndResponseDicomJson(); + + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new QueryInstancesOfSeriesOfStudiesController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js index 5fbd2399..932e97b6 100644 --- a/api-sql/dicom-web/qido-rs.route.js +++ b/api-sql/dicom-web/qido-rs.route.js @@ -193,12 +193,12 @@ router.get( * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" */ -// router.get( -// "/studies/:studyUID/series/:seriesUID/instances", validateParams(queryValidation, "query", { -// allowUnknown: true -// }), -// require("./controller/QIDO-RS/queryStudies-Series-Instance") -// ); +router.get( + "/studies/:studyUID/series/:seriesUID/instances", validateParams(queryValidation, "query", { + allowUnknown: true + }), + require("./controller/QIDO-RS/queryStudies-Series-Instance") +); /** * @openapi From adf37e3957c0f8e1e3d2051d70107ebd49765d92 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 15:14:30 +0800 Subject: [PATCH 055/365] refactor: extract query process to function --- .../QIDO-RS/service/querybuilder.js | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 8d57afd6..c0102ae5 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -17,20 +17,7 @@ class BaseQueryBuilder { build() { for (let key in this.queryOptions.query) { - let commaValue = this.comma(key, this.queryOptions.query[key]); - - for (let i = 0; i < commaValue.length; i++) { - let value = this.getWildCardQuery(commaValue[i][key]); - try { - if (key.includes(".")) { - this[key](value); - } else { - this[`get${dictionary.tag[key]}`](value); - } - } catch (e) { - if (e.message.includes("not a function")) break; - } - } + this.getQueryByParam_(key); } let sequelizeQuery = { @@ -48,6 +35,32 @@ class BaseQueryBuilder { return sequelizeQuery; } + /** + * @private + * @param {string} key + */ + getQueryByParam_(key) { + let value = this.queryOptions.query[key]; + let values = Array.isArray(value) ? value: [value]; + + for (let i = 0; i < values.length; i++) { + let paramValue = values[i]; + let commaValue = this.comma(key, paramValue); + for (let i = 0; i < commaValue.length; i++) { + let value = this.getWildCardQuery(commaValue[i][key]); + try { + if (key.includes(".")) { + this[key](value); + } else { + this[`get${dictionary.tag[key]}`](value); + } + } catch (e) { + if (e.message.includes("not a function")) break; + } + } + } + } + getSequelizeIncludePersonNameQuery() { let includes = []; From c7701f7620b69064f01e07cb784aae4ebf4b9cf8 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 15:42:24 +0800 Subject: [PATCH 056/365] feat: patient query builder --- .../QIDO-RS/service/patientQueryBuilder.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js new file mode 100644 index 00000000..ba7cfed7 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js @@ -0,0 +1,50 @@ +const _ = require("lodash"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { BaseQueryBuilder } = require("./querybuilder"); +const sequelize = require("@models/sql/instance"); +const { PersonNameModel } = require("@models/sql/models/personName.model"); + +class PatientQueryBuilder extends BaseQueryBuilder { + constructor(queryOptions) { + super(queryOptions); + this.query = {}; + } + + getPatientName(value) { + let { query } = this.getPersonNameQuery(dictionary.keyword.PatientName, value); + this.includeQueries.push({ + model: PersonNameModel, + required: true, + where: { + ...query + } + }); + } + + getPatientID(value) { + let q = this.getStringQuery(dictionary.keyword.PatientID, value); + this.query = { + ...this.query, + ...q + }; + } + + getPatientBirthDate(value) { + let q = this.getDateQuery(dictionary.keyword.PatientBirthDate, value); + this.query = { + ...this.query, + ...q + }; + } + + getIssuerOfPatientID(value) { + let q = this.getStringQuery(dictionary.keyword.IssuerOfPatientID, value); + this.query = { + ...this.query, + ...q + }; + } + +} + +module.exports.PatientQueryBuilder = PatientQueryBuilder; \ No newline at end of file From 34103a581fc32a6fa896ad80415d64ee7fe853fd Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 15:42:50 +0800 Subject: [PATCH 057/365] feat: search patient API --- .../controller/QIDO-RS/allPatient.js | 46 +++++++++++++++++++ .../QIDO-RS/service/QIDO-RS.service.js | 4 +- api-sql/dicom-web/qido-rs.route.js | 8 ++-- models/sql/models/patient.model.js | 19 ++++++++ 4 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/allPatient.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/allPatient.js b/api-sql/dicom-web/controller/QIDO-RS/allPatient.js new file mode 100644 index 00000000..b2034f3e --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/allPatient.js @@ -0,0 +1,46 @@ +const { + SqlQidoRsService: QidoRsService +} = require("./service/QIDO-RS.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class QueryAllPatientsController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "QIDO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info("Query all patients"); + + try { + let qidoRsService = new QidoRsService(this.request, this.response, "patient"); + + await qidoRsService.getAndResponseDicomJson(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new QueryAllPatientsController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 6954a337..461158bb 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -11,6 +11,7 @@ const { const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); const { InstanceModel } = require("@models/sql/models/instance.mode"); +const { PatientModel } = require("@models/sql/models/patient.model"); class SqlQidoRsService extends QidoRsService { @@ -85,7 +86,7 @@ class QidoDicomJsonFactory { this.getDicomJsonByLevel = { "patient": async () => { - // return await getPatientDicomJson(queryOptions); + return await PatientModel.getDicomJson(queryOptions); }, "study": async () => { return await StudyModel.getDicomJson(queryOptions); @@ -94,7 +95,6 @@ class QidoDicomJsonFactory { return await SeriesModel.getDicomJson(queryOptions); }, "instance": async () => { - // return await getInstanceDicomJson(queryOptions); return await InstanceModel.getDicomJson(queryOptions); } }; diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js index 932e97b6..584adc32 100644 --- a/api-sql/dicom-web/qido-rs.route.js +++ b/api-sql/dicom-web/qido-rs.route.js @@ -301,10 +301,10 @@ router.get( * allOf: * - $ref: "#/components/schemas/PatientRequiredMatchingAttributes" */ -// router.get( -// "/patients", -// require("./controller/QIDO-RS/allPatient") -// ); +router.get( + "/patients", + require("./controller/QIDO-RS/allPatient") +); //#endregion diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index 9ce68d29..29a08a18 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -1,6 +1,7 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); +const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); class PatientModel extends Model {}; @@ -47,4 +48,22 @@ PatientModel.init({ freezeTableName: true }); +PatientModel.getDicomJson = async function (queryOptions) { + let queryBuilder = new PatientQueryBuilder(queryOptions); + let q = queryBuilder.build(); + let studies = await PatientModel.findAll({ + ...q, + attributes: ["json"], + limit: queryOptions.limit, + offset: queryOptions.skip + }); + + + return await Promise.all(studies.map(async study => { + let { json } = study.toJSON(); + + return json; + })); +}; + module.exports.PatientModel = PatientModel; From 5c9b2231fa02ac244e547301566e8453f6f436bb Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 15:52:21 +0800 Subject: [PATCH 058/365] fix: incorrect result of querying instance in study # Problem - When search /studies/2.16.840.1.113995.3.110.3.0.10118.2000002.278819.649182/instances - , it should response 10 result but got 17 - The issues is using left outer join rather than inner join # Solutions - Use inner join instead of outer join - Add required in sequelize query options to do inner join --- .../controller/QIDO-RS/service/instanceQueryBuilder.js | 3 ++- .../dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js index 559f3b6c..ea45f279 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js @@ -42,7 +42,8 @@ class InstanceQueryBuilder extends BaseQueryBuilder { this.includeQueries.push({ model: sequelize.model("Series"), attributes: ["x0020000E"], - ...seriesQuery + ...seriesQuery, + required: true }); let instanceUidInParams = _.get(this.queryOptions.requestParams, "instanceUID"); diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index d6694996..eff06601 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -24,7 +24,8 @@ class SeriesQueryBuilder extends BaseQueryBuilder { this.includeQueries.push({ model: sequelize.model("Study"), attributes: ["x0020000D"], - ...studyQuery + ...studyQuery, + required: true }); let seriesInstanceUidInParams = _.get(this.queryOptions.requestParams, "seriesUID"); From 45a8fcd9c5ae4852eff6cf1db4ec313799b048a4 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Thu, 10 Aug 2023 17:38:40 +0800 Subject: [PATCH 059/365] fix: cannot query with comma of PN attributes # Problems - Use spread syntax would be overwrite value # Solutions - Combine value with [Op.or] to support comma or search --- .../QIDO-RS/service/instanceQueryBuilder.js | 56 ++++++--- .../QIDO-RS/service/patientQueryBuilder.js | 50 ++++++-- .../QIDO-RS/service/querybuilder.js | 111 +++++++++++++++--- .../QIDO-RS/service/seriesQueryBuilder.js | 56 +++++++-- 4 files changed, 222 insertions(+), 51 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js index ea45f279..bb4a0816 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js @@ -7,6 +7,7 @@ const { VerifyIngObserverSqModel } = require("@models/sql/models/verifyingObserv const { PersonNameModel } = require("@models/sql/models/personName.model"); const sequelize = require("@models/sql/instance"); const { SeriesQueryBuilder } = require("./seriesQueryBuilder"); +const { Op } = require("sequelize"); class InstanceQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { @@ -35,7 +36,7 @@ class InstanceQueryBuilder extends BaseQueryBuilder { this["0040A073.0040A075"] = VerifyingObserverQueryBuilder.prototype.getName.bind(verifyingObserverQueryBuilder); this["0040A073.0040A030"] = VerifyingObserverQueryBuilder.prototype.getDateTime.bind(verifyingObserverQueryBuilder); this["0040A073.0040A027"] = VerifyingObserverQueryBuilder.prototype.getOrganization.bind(verifyingObserverQueryBuilder); - + let seriesQueryBuilder = new SeriesQueryBuilder(queryOptions); let seriesQuery = seriesQueryBuilder.build(); @@ -122,7 +123,7 @@ class ConceptNameCodeSqQueryBuilder { } isModelIncluded() { - return this.instanceQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "ConceptNameCodeSQ"); + return this.instanceQueryBuilder.includeQueries.find(v => v.model.getTableName() === "ConceptNameCodeSQ"); } getCodeValue(value) { @@ -136,7 +137,7 @@ class ConceptNameCodeSqQueryBuilder { } getCodingSchemeVersion(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion ,value); + let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion, value); this.addQuery(q); } @@ -172,13 +173,13 @@ class ContentSqQueryBuilder { } isModelIncluded() { - return this.instanceQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "DicomContentSQ"); + return this.instanceQueryBuilder.includeQueries.find(v => v.model.getTableName() === "DicomContentSQ"); } isConceptNameCodeInclude() { let currentModel = this.isModelIncluded(); if (currentModel && currentModel.include) { - return currentModel.include.find( v => v.model.getTableName() === "ConceptNameCode"); + return currentModel.include.find(v => v.model.getTableName() === "ConceptNameCode"); } return false; } @@ -186,7 +187,7 @@ class ContentSqQueryBuilder { isConceptCodeInclude() { let currentModel = this.isModelIncluded(); if (currentModel && currentModel.include) { - return currentModel.include.find( v=> v.model.getTableName() === "ConceptCode"); + return currentModel.include.find(v => v.model.getTableName() === "ConceptCode"); } return false; } @@ -226,7 +227,7 @@ class ContentSqQueryBuilder { this.addConceptNameCodeQuery(q); } - getConceptCodeValue (value) { + getConceptCodeValue(value) { let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeValue, value); this.addConceptCodeQuery(q); } @@ -266,7 +267,7 @@ class ContentSqQueryBuilder { addConceptNameCodeQuery(q) { if (!this.conceptNameCodeInclude) { - this.conceptNameCodeInclude = { + this.conceptNameCodeInclude = { model: DicomCodeModel, as: "ConceptNameCode", where: { @@ -296,7 +297,7 @@ class ContentSqQueryBuilder { addConceptCodeQuery(q) { if (!this.conceptCodeInclude) { - this.conceptCodeInclude = { + this.conceptCodeInclude = { model: DicomCodeModel, as: "ConceptCode", where: { @@ -332,36 +333,53 @@ class VerifyingObserverQueryBuilder { } isModelIncluded() { - return this.instanceQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "VerifyingObserverSQ"); + return this.instanceQueryBuilder.includeQueries.find(v => v.model.getTableName() === "VerifyingObserverSQ"); } isPersonNameIncluded() { let currentModel = this.isModelIncluded(); if (currentModel && currentModel.include) { - return currentModel.include.find( v => v.model.getTableName() === "PersonName"); + return currentModel.include.find(v => v.model.getTableName() === "PersonName"); } return false; } getName(value) { - let q = this.instanceQueryBuilder.getPersonNameQuery(dictionary.keyword.VerifyingObserverName, value); + let { query } = this.instanceQueryBuilder.getPersonNameQuery(dictionary.keyword.VerifyingObserverName, value); this.addQuery({}); let currentModel = this.isModelIncluded(); currentModel.include = currentModel.include ? currentModel.include : []; let personNameIncluded = this.isPersonNameIncluded(); - if (personNameIncluded) { - personNameIncluded.where = { - ...personNameIncluded.where, - ...q.query - }; - } else { + if (!personNameIncluded) { currentModel.include.push({ model: PersonNameModel, where: { - ...q.query + [Op.or]: [ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ] }, attributes: [] }); + } else { + personNameIncluded.where[Op.or].push(...[ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ]); } } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js index ba7cfed7..c9b22e9b 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js @@ -3,6 +3,7 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { BaseQueryBuilder } = require("./querybuilder"); const sequelize = require("@models/sql/instance"); const { PersonNameModel } = require("@models/sql/models/personName.model"); +const { Op } = require("sequelize"); class PatientQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { @@ -10,15 +11,50 @@ class PatientQueryBuilder extends BaseQueryBuilder { this.query = {}; } + getIncludedPersonNameModel() { + if (this.includeQueries.length > 0) { + return this.includeQueries.find(v => v.model.getTableName() === "PersonName"); + } + return undefined; + } + getPatientName(value) { let { query } = this.getPersonNameQuery(dictionary.keyword.PatientName, value); - this.includeQueries.push({ - model: PersonNameModel, - required: true, - where: { - ...query - } - }); + + let includedPersonNameModel = this.getIncludedPersonNameModel(); + if (!includedPersonNameModel) { + this.includeQueries.push({ + model: PersonNameModel, + required: true, + where: { + [Op.or]: [ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ] + } + }); + } else { + includedPersonNameModel.where[Op.or] = [ + ...includedPersonNameModel.where[Op.or], + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ]; + } + } getPatientID(value) { diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index c0102ae5..dab6ebc5 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -4,7 +4,6 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { Op, Sequelize, cast, col } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); const { PersonNameModel } = require("@models/sql/models/personName.model"); -const { PatientModel } = require("@models/sql/models/patient.model"); const sequelize = require("@models/sql/instance"); class BaseQueryBuilder { @@ -41,7 +40,7 @@ class BaseQueryBuilder { */ getQueryByParam_(key) { let value = this.queryOptions.query[key]; - let values = Array.isArray(value) ? value: [value]; + let values = Array.isArray(value) ? value : [value]; for (let i = 0; i < values.length; i++) { let paramValue = values[i]; @@ -310,6 +309,28 @@ class StudyQueryBuilder extends BaseQueryBuilder { } } + getIncludedPatientModel() { + if (this.includeQueries.length > 0) { + return this.includeQueries.find(v => v.model.getTableName() === "Patient"); + } + return undefined; + } + + getIncludedPersonNameModelInPatient() { + let includedPatientModel = this.getIncludedPatientModel(); + if (includedPatientModel) { + return includedPatientModel.include.find(v => v.model.getTableName() === "PersonName"); + } + return undefined; + } + + getIncludedPersonNameModel() { + if (this.includeQueries.length > 0) { + return this.includeQueries.find(v => v.model.getTableName() === "PersonName"); + } + return undefined; + } + getStudyInstanceUID(value) { let q = this.getStringQuery(dictionary.keyword.StudyInstanceUID, value); @@ -320,16 +341,45 @@ class StudyQueryBuilder extends BaseQueryBuilder { } getPatientName(value) { - let q = this.getPersonNameQuery(dictionary.keyword.PatientName, value); - this.includeQueries.push({ - model: PatientModel, - include: [{ - model: PersonNameModel, - where: q.query, + let { query } = this.getPersonNameQuery(dictionary.keyword.PatientName, value); + let includedPatientModel = this.getIncludedPatientModel(); + if (!includedPatientModel) { + this.includeQueries.push({ + model: sequelize.model("Patient"), + include: [{ + model: sequelize.model("PersonName"), + where: { + [Op.or]: [ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ] + }, + required: true + }], required: true - }], - required: true - }); + }); + } else { + let personNameModel = this.getIncludedPersonNameModelInPatient(); + personNameModel.where[Op.or].push(...[ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ]); + } + } getPatientID(value) { @@ -376,12 +426,39 @@ class StudyQueryBuilder extends BaseQueryBuilder { } getReferringPhysicianName(value) { - let q = this.getPersonNameQuery(dictionary.keyword.ReferringPhysicianName, value); - this.includeQueries.push({ - model: PersonNameModel, - where: q.where, - required: true - }); + let { query } = this.getPersonNameQuery(dictionary.keyword.ReferringPhysicianName, value); + let includedPersonNameModel = this.getIncludedPersonNameModel(); + if (!includedPersonNameModel) { + this.includeQueries.push({ + model: PersonNameModel, + where: { + [Op.or]: [ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ] + }, + required: true + }); + } else { + includedPersonNameModel.where[Op.or].push(...[ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ]); + } } getStudyID(value) { diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index eff06601..e512a480 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -35,6 +35,20 @@ class SeriesQueryBuilder extends BaseQueryBuilder { }; } } + + getIncludedPerformingPhysicianNameModel() { + if (this.includeQueries.length > 0) { + return this.includeQueries.find(v=> _.get(v, "as", "") === "performingPhysicianName"); + } + return undefined; + } + + getIncludedOperatorsNameModel() { + if (this.includeQueries.length > 0) { + return this.includeQueries.find(v=> _.get(v, "as", "") === "operatorsName"); + } + return undefined; + } getSeriesDate(value) { let q = this.getDateQuery(dictionary.keyword.SeriesDate, value); this.query = { @@ -71,14 +85,40 @@ class SeriesQueryBuilder extends BaseQueryBuilder { getPerformingPhysicianName(value) { let { query } = this.getPersonNameQuery(dictionary.keyword.PerformingPhysicianName, value); - this.includeQueries.push({ - model: PersonNameModel, - as: "performingPhysicianName", - where: { - ...query - }, - attributes: [] - }); + let includedPerformingPhysicianNameModel = this.getIncludedPerformingPhysicianNameModel(); + if (!includedPerformingPhysicianNameModel) { + this.includeQueries.push({ + model: PersonNameModel, + as: "performingPhysicianName", + where: { + [Op.or]: [ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ] + }, + attributes: [] + }); + } else { + includedPerformingPhysicianNameModel.where[Op.or].push(...[ + { + alphabetic: query[Op.or].alphabetic + }, + { + ideographic: query[Op.or].ideographic + }, + { + phonetic: query[Op.or].phonetic + } + ]); + } + } getOperatorsName(value) { From 4663e116e4fc5477932642d381b92177960159a2 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Fri, 11 Aug 2023 15:45:12 +0800 Subject: [PATCH 060/365] fix: cannot query with comma of attributes - Use spread syntax would be overwrite value - Combine value with [Op.or] to support comma or search - Refactor code of combining query flow 1. Process comma 2. Convert every value to wildcard search 3. Every argument of query function input is value array rather than single value fix(WIP): cannot query with comma of attributes - Use spread syntax would be overwrite value - Combine value with [Op.or] to support comma or search - Refactor code of combining query flow 1. Process comma 2. Convert every value to wildcard search 3. Every argument of query function input is value array rather than single value --- .../QIDO-RS/service/instanceQueryBuilder.js | 244 ++++++++++-------- .../QIDO-RS/service/patientQueryBuilder.js | 52 +--- .../QIDO-RS/service/querybuilder.js | 198 ++++++-------- .../QIDO-RS/service/seriesQueryBuilder.js | 148 ++++++----- 4 files changed, 294 insertions(+), 348 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js index bb4a0816..655ce353 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js @@ -22,7 +22,7 @@ class InstanceQueryBuilder extends BaseQueryBuilder { let contentQueryBuilder = new ContentSqQueryBuilder(this); this["0040A730.0040A040"] = ContentSqQueryBuilder.prototype.getValueType.bind(contentQueryBuilder); this["0040A730.0040A010"] = ContentSqQueryBuilder.prototype.getRelationshipType.bind(contentQueryBuilder); - this["0040A730.0040A160"] = ContentSqQueryBuilder.prototype.getValueType.bind(contentQueryBuilder); + this["0040A730.0040A160"] = ContentSqQueryBuilder.prototype.getTextValue.bind(contentQueryBuilder); this["0040A730.0040A043.00080100"] = ContentSqQueryBuilder.prototype.getConceptNameCodeValue.bind(contentQueryBuilder); this["0040A730.0040A043.00080102"] = ContentSqQueryBuilder.prototype.getConceptNameCodingSchemeDesignator.bind(contentQueryBuilder); this["0040A730.0040A043.00080103"] = ContentSqQueryBuilder.prototype.getConceptNameCodingSchemeVersion.bind(contentQueryBuilder); @@ -57,62 +57,42 @@ class InstanceQueryBuilder extends BaseQueryBuilder { /** * - * @param {string} value + * @param {string[]} values */ - getSOPClassUID(value) { - let q = this.getStringQuery(dictionary.keyword.SOPClassUID, value); - this.query = { - ...this.query, - ...q - }; + getSOPClassUID(values) { + return this.getOrQuery(dictionary.keyword.SOPClassUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } /** * - * @param {string} value + * @param {string[]} values */ - getSOPInstanceUID(value) { - let q = this.getStringQuery(dictionary.keyword.SOPInstanceUID, value); - this.query = { - ...this.query, - ...q - }; + getSOPInstanceUID(values) { + return this.getOrQuery(dictionary.keyword.SOPInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } /** * - * @param {string} value + * @param {string[]} values */ - getContentDate(value) { - let q = this.getDateQuery(dictionary.keyword.ContentDate, value); - this.query = { - ...this.query, - ...q - }; + getContentDate(values) { + return this.getOrQuery(dictionary.keyword.ContentDate, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } /** * - * @param {string} value + * @param {string[]} values */ - getContentTime(value) { - let q = this.getTimeQuery(dictionary.keyword.ContentTime, value); - this.query = { - ...this.query, - ...q - }; + getContentTime(values) { + return this.getOrQuery(dictionary.keyword.ContentTime, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } /** * - * @param {string} value + * @param {string[]} values */ - getInstanceNumber(value) { - let q = this.getStringQuery(dictionary.keyword.InstanceNumber, value); - this.query = { - ...this.query, - ...q - }; + getInstanceNumber(values) { + return this.getOrQuery(dictionary.keyword.InstanceNumber, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } } @@ -176,92 +156,138 @@ class ContentSqQueryBuilder { return this.instanceQueryBuilder.includeQueries.find(v => v.model.getTableName() === "DicomContentSQ"); } - isConceptNameCodeInclude() { + getConceptNameCodeInclude() { let currentModel = this.isModelIncluded(); if (currentModel && currentModel.include) { return currentModel.include.find(v => v.model.getTableName() === "ConceptNameCode"); } - return false; + return undefined; } - isConceptCodeInclude() { + getConceptCodeInclude() { let currentModel = this.isModelIncluded(); if (currentModel && currentModel.include) { return currentModel.include.find(v => v.model.getTableName() === "ConceptCode"); } - return false; + return undefined; } - getValueType(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.ValueType, value); + getValueType(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.ValueType, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addQuery(q); } getTextValue(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.TextValue, value); + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.TextValue, + value, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addQuery(q); } - getRelationshipType(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.RelationshipType, value); + getRelationshipType(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.RelationshipType, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addQuery(q); } - getConceptNameCodeValue(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeValue, value); + getConceptNameCodeValue(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodeValue, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptNameCodeQuery(q); } - getConceptNameCodingSchemeDesignator(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeDesignator, value); + getConceptNameCodingSchemeDesignator(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodingSchemeDesignator, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptNameCodeQuery(q); } - getConceptNameCodingSchemeVersion(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion, value); + getConceptNameCodingSchemeVersion(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodingSchemeVersion, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptNameCodeQuery(q); } - getConceptNameCodeMeaning(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeMeaning, value); + getConceptNameCodeMeaning(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodeMeaning, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptNameCodeQuery(q); } - getConceptCodeValue(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeValue, value); + getConceptCodeValue(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodeValue, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptCodeQuery(q); } - getConceptCodingSchemeDesignator(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeDesignator, value); + getConceptCodingSchemeDesignator(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodingSchemeDesignator, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptCodeQuery(q); } - getConceptCodingSchemeVersion(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion, value); + getConceptCodingSchemeVersion(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodingSchemeVersion, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptCodeQuery(q); } - getConceptCodeMeaning(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeMeaning, value); + getConceptCodeMeaning(values) { + let q = this.getOrQuery( + dictionary.keyword.CodeMeaning, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addConceptCodeQuery(q); } addQuery(q) { let currentModel = this.isModelIncluded(); - if (currentModel) { - currentModel.where = { - ...currentModel.where, - ...q - }; - } else { + if (!currentModel) { this.instanceQueryBuilder.includeQueries.push({ model: DicomContentSqModel, where: { - ...q + [Op.and]: [ + q + ] }, attributes: [] }); + } else { + currentModel.where[Op.and] = { + ...currentModel.where[Op.and], + q + }; } } @@ -271,18 +297,20 @@ class ContentSqQueryBuilder { model: DicomCodeModel, as: "ConceptNameCode", where: { - ...q + [Op.and]: [ + q + ] }, attributes: [] }; } else { - this.conceptNameCodeInclude.where = { - ...this.conceptNameCodeInclude.where, - ...q + this.conceptNameCodeInclude.where[Op.and] = { + ...this.conceptNameCodeInclude.where[Op.and], + q }; } - let conceptNameIncluded = this.isConceptNameCodeInclude(); + let conceptNameIncluded = this.getConceptNameCodeInclude(); if (conceptNameIncluded) { conceptNameIncluded = this.conceptNameCodeInclude; @@ -290,7 +318,7 @@ class ContentSqQueryBuilder { this.addQuery({}); let currentModel = this.isModelIncluded(); currentModel.include = currentModel.include ? currentModel.include : []; - conceptNameIncluded = this.isConceptNameCodeInclude(); + conceptNameIncluded = this.getConceptNameCodeInclude(); currentModel.include.push(this.conceptNameCodeInclude); } } @@ -312,7 +340,7 @@ class ContentSqQueryBuilder { }; } - let conceptCodeIncluded = this.isConceptCodeInclude(); + let conceptCodeIncluded = this.getConceptCodeInclude(); if (conceptCodeIncluded) { conceptCodeIncluded = this.conceptCodeInclude; @@ -320,7 +348,7 @@ class ContentSqQueryBuilder { this.addQuery({}); let currentModel = this.isModelIncluded(); currentModel.include = currentModel.include ? currentModel.include : []; - conceptCodeIncluded = this.isConceptCodeInclude(); + conceptCodeIncluded = this.getConceptCodeInclude(); currentModel.include.push(this.conceptCodeInclude); } } @@ -344,8 +372,12 @@ class VerifyingObserverQueryBuilder { return false; } - getName(value) { - let { query } = this.instanceQueryBuilder.getPersonNameQuery(dictionary.keyword.VerifyingObserverName, value); + getName(values) { + let query = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.VerifyingObserverName, + values, + BaseQueryBuilder.prototype.getPersonNameQuery.bind(this.instanceQueryBuilder) + ); this.addQuery({}); let currentModel = this.isModelIncluded(); currentModel.include = currentModel.include ? currentModel.include : []; @@ -354,60 +386,48 @@ class VerifyingObserverQueryBuilder { currentModel.include.push({ model: PersonNameModel, where: { - [Op.or]: [ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ] + [Op.or]: query[Op.or] }, attributes: [] }); - } else { - personNameIncluded.where[Op.or].push(...[ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ]); } } - getDateTime(value) { - let q = this.instanceQueryBuilder.getDateQuery(dictionary.keyword.VerificationDateTime, value); + getDateTime(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.VerificationDateTime, + values, + BaseQueryBuilder.prototype.getDateTimeQuery.bind(this.instanceQueryBuilder) + ); this.addQuery(q); } - getOrganization(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.VerifyingOrganization, value); + getOrganization(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.VerifyingOrganization, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); this.addQuery(q); } addQuery(q) { let currentModel = this.isModelIncluded(); - if (currentModel) { - currentModel.where = { - ...currentModel.where, - ...q - }; - } else { + if (!currentModel) { this.instanceQueryBuilder.includeQueries.push({ model: VerifyIngObserverSqModel, where: { - ...q + [Op.and]: [ + q + ] }, attributes: [] }); + } else { + currentModel.where[Op.and] = [ + ...currentModel.where[Op.and], + q + ]; } } } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js index c9b22e9b..96d962aa 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js @@ -8,7 +8,6 @@ const { Op } = require("sequelize"); class PatientQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { super(queryOptions); - this.query = {}; } getIncludedPersonNameModel() { @@ -18,8 +17,8 @@ class PatientQueryBuilder extends BaseQueryBuilder { return undefined; } - getPatientName(value) { - let { query } = this.getPersonNameQuery(dictionary.keyword.PatientName, value); + getPatientName(values) { + let query = this.getOrQuery(dictionary.keyword.PatientName, values, this.getPersonNameQuery.bind(this)); let includedPersonNameModel = this.getIncludedPersonNameModel(); if (!includedPersonNameModel) { @@ -27,58 +26,23 @@ class PatientQueryBuilder extends BaseQueryBuilder { model: PersonNameModel, required: true, where: { - [Op.or]: [ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ] + [Op.or]: query[Op.or] } }); - } else { - includedPersonNameModel.where[Op.or] = [ - ...includedPersonNameModel.where[Op.or], - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ]; - } + } } - getPatientID(value) { - let q = this.getStringQuery(dictionary.keyword.PatientID, value); - this.query = { - ...this.query, - ...q - }; + getPatientID(values) { + return this.getOrQuery(dictionary.keyword.PatientID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } getPatientBirthDate(value) { - let q = this.getDateQuery(dictionary.keyword.PatientBirthDate, value); - this.query = { - ...this.query, - ...q - }; + return this.getOrQuery(dictionary.keyword.PatientBirthDate, value, BaseQueryBuilder.prototype.getDateQuery.bind(this)); } getIssuerOfPatientID(value) { - let q = this.getStringQuery(dictionary.keyword.IssuerOfPatientID, value); - this.query = { - ...this.query, - ...q - }; + return this.getOrQuery(dictionary.keyword.IssuerOfPatientID, value, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index dab6ebc5..d79f4247 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -5,6 +5,7 @@ const { Op, Sequelize, cast, col } = require("sequelize"); const { raccoonConfig } = require("@root/config-class"); const { PersonNameModel } = require("@models/sql/models/personName.model"); const sequelize = require("@models/sql/instance"); +const { logger } = require("@root/utils/logs/log"); class BaseQueryBuilder { constructor(queryOptions) { @@ -12,6 +13,9 @@ class BaseQueryBuilder { /** @type {import("sequelize").IncludeOptions[]} */ this.includeQueries = []; this.bind = []; + this.query = { + [Op.and]: [] + }; } build() { @@ -45,18 +49,25 @@ class BaseQueryBuilder { for (let i = 0; i < values.length; i++) { let paramValue = values[i]; let commaValue = this.comma(key, paramValue); - for (let i = 0; i < commaValue.length; i++) { - let value = this.getWildCardQuery(commaValue[i][key]); - try { - if (key.includes(".")) { - this[key](value); - } else { - this[`get${dictionary.tag[key]}`](value); - } - } catch (e) { - if (e.message.includes("not a function")) break; + let wildCardValues = commaValue.map(v => this.getWildCardQuery(v[key])); + try { + let query; + if (key.includes(".")) { + query = this[key](wildCardValues); + } else { + query = this[`get${dictionary.tag[key]}`](wildCardValues); } + + this.query[Op.and] = [ + ...this.query[Op.and], + query + ]; + } catch (e) { + if (e.message.includes("not a function")) break; + logger.error(e); + throw e; } + } } @@ -99,6 +110,27 @@ class BaseQueryBuilder { }; } + /** + * + * @param {string} tag + * @param {string[]} values + * @param {(tag: string, values: string[]) => void} queryFn + */ + getOrQuery(tag, values, queryFn) { + if (values.length === 1) { + return queryFn(tag, values[0]); + } + + let or = { + [Op.or]: [] + }; + for (let i = 0; i < values.length; i++) { + let q = queryFn(tag, values[i]); + or[Op.or].push(q); + } + return or; + } + getStringArrayJsonQuery(tag, value) { if (raccoonConfig.sqlDbConfig.dialect === "postgres") { return { @@ -121,37 +153,39 @@ class BaseQueryBuilder { /** * + * @param {string} tag * @param {string} value * @returns */ getPersonNameQuery(tag, value) { if (value.includes("%") || value.includes("_")) { return { - query: { - [Op.or]: { + [Op.or]: [ + { alphabetic: { [Op.like]: value - }, + } + }, + { ideographic: { [Op.like]: value - }, + } + }, + { phonetic: { [Op.like]: value } } - }, - field: tag + ] }; } + return { - query: { - [Op.or]: { - alphabetic: value, - ideographic: value, - phonetic: value - } - }, - field: tag + [Op.or]: [ + { alphabetic: value }, + { ideographic: value }, + { phonetic: value } + ] }; } @@ -299,7 +333,6 @@ class BaseQueryBuilder { class StudyQueryBuilder extends BaseQueryBuilder { constructor(queryOptions) { super(queryOptions); - this.query = {}; let studyInstanceUidInParams = _.get(this.queryOptions.requestParams, "studyUID"); if (studyInstanceUidInParams) { @@ -332,16 +365,13 @@ class StudyQueryBuilder extends BaseQueryBuilder { } - getStudyInstanceUID(value) { - let q = this.getStringQuery(dictionary.keyword.StudyInstanceUID, value); - this.query = { - ...this.query, - ...q - }; + getStudyInstanceUID(values) { + return this.getOrQuery(dictionary.keyword.StudyInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } - getPatientName(value) { - let { query } = this.getPersonNameQuery(dictionary.keyword.PatientName, value); + getPatientName(values) { + let query = this.getOrQuery(dictionary.keyword.PatientName, values, BaseQueryBuilder.prototype.getPersonNameQuery.bind(this)); + let includedPatientModel = this.getIncludedPatientModel(); if (!includedPatientModel) { this.includeQueries.push({ @@ -349,124 +379,60 @@ class StudyQueryBuilder extends BaseQueryBuilder { include: [{ model: sequelize.model("PersonName"), where: { - [Op.or]: [ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ] + [Op.or]: query[Op.or] }, required: true }], required: true }); - } else { - let personNameModel = this.getIncludedPersonNameModelInPatient(); - personNameModel.where[Op.or].push(...[ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ]); - } - + } } - getPatientID(value) { - let q = this.getStringQuery(dictionary.keyword.PatientID, value); - this.query = { - ...this.query, - ...q - }; + getPatientID(values) { + return this.getOrQuery(dictionary.keyword.PatientID, values, this.getStringQuery); } - getStudyDate(value) { - let q = this.getDateQuery(dictionary.keyword.StudyDate, value); - this.query = { - ...this.query, - ...q - }; + getStudyDate(values) { + return this.getOrQuery(dictionary.keyword.StudyDate, values, BaseQueryBuilder.prototype.getDateQuery.bind(this)); } - getStudyTime(value) { - let q = this.getTimeQuery(dictionary.keyword.StudyTime, value); - this.query = { - ...this.query, - ...q - }; + getStudyTime(values) { + return this.getOrQuery(dictionary.keyword.StudyTime, values, BaseQueryBuilder.prototype.getTimeQuery.bind(this)); } - getAccessionNumber(value) { - let q = this.getStringQuery(dictionary.keyword.AccessionNumber, value); - this.query = { - ...this.query, - ...q - }; + getAccessionNumber(values) { + return this.getOrQuery(dictionary.keyword.AccessionNumber, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } - getModalitiesInStudy(value) { - let q = this.getStringQuery(dictionary.keyword.Modality, value); + getModalitiesInStudy(values) { + let stringQuery = this.getOrQuery(dictionary.keyword.Modality, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); this.includeQueries.push({ model: sequelize.model("Series"), where: { - ...q + ...stringQuery }, attributes: [] }); } - getReferringPhysicianName(value) { - let { query } = this.getPersonNameQuery(dictionary.keyword.ReferringPhysicianName, value); + getReferringPhysicianName(values) { + let query = this.getOrQuery(dictionary.keyword.ReferringPhysicianName, values, BaseQueryBuilder.prototype.getPersonNameQuery.bind(this)); let includedPersonNameModel = this.getIncludedPersonNameModel(); if (!includedPersonNameModel) { this.includeQueries.push({ model: PersonNameModel, where: { [Op.or]: [ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } + ...query[Op.or] ] }, required: true }); - } else { - includedPersonNameModel.where[Op.or].push(...[ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ]); - } + } } - getStudyID(value) { - let q = this.getStringQuery(dictionary.keyword.StudyID, value); - this.query = { - ...this.query, - ...q - }; + getStudyID(values) { + return this.getOrQuery(dictionary.keyword.StudyID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } } diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index e512a480..6f5e8569 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -18,7 +18,7 @@ class SeriesQueryBuilder extends BaseQueryBuilder { this["00400275.00321033"] = SeriesRequestAttributeSequence.prototype.getRequestingService.bind(seriesRequestAttributeSequence); this["00400275.00401001"] = SeriesRequestAttributeSequence.prototype.getRequestedProcedureID.bind(seriesRequestAttributeSequence); this["00400275.0020000D"] = SeriesRequestAttributeSequence.prototype.getStudyInstanceUID.bind(seriesRequestAttributeSequence); - + let studyQueryBuilder = new StudyQueryBuilder(queryOptions); let studyQuery = studyQueryBuilder.build(); this.includeQueries.push({ @@ -38,37 +38,25 @@ class SeriesQueryBuilder extends BaseQueryBuilder { getIncludedPerformingPhysicianNameModel() { if (this.includeQueries.length > 0) { - return this.includeQueries.find(v=> _.get(v, "as", "") === "performingPhysicianName"); + return this.includeQueries.find(v => _.get(v, "as", "") === "performingPhysicianName"); } return undefined; } getIncludedOperatorsNameModel() { if (this.includeQueries.length > 0) { - return this.includeQueries.find(v=> _.get(v, "as", "") === "operatorsName"); + return this.includeQueries.find(v => _.get(v, "as", "") === "operatorsName"); } return undefined; } - getSeriesDate(value) { - let q = this.getDateQuery(dictionary.keyword.SeriesDate, value); - this.query = { - ...this.query, - ...q - }; + getSeriesDate(values) { + return this.getOrQuery(dictionary.keyword.SeriesDate, values, BaseQueryBuilder.prototype.getDateQuery.bind(this)); } - getModality(value) { - let q = this.getStringQuery(dictionary.keyword.Modality, value); - this.query = { - ...this.query, - ...q - }; + getModality(values) { + return this.getOrQuery(dictionary.keyword.Modality, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } - getSeriesDescription(value) { - let q = this.getStringQuery(dictionary.keyword.SeriesDescription, value); - this.query = { - ...this.query, - ...q - }; + getSeriesDescription(values) { + return this.getOrQuery(dictionary.keyword.SeriesDescription, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } getPersonNameJsonArrayQuery(tag, value) { @@ -83,62 +71,36 @@ class SeriesQueryBuilder extends BaseQueryBuilder { throw new Error("Not implemented"); } - getPerformingPhysicianName(value) { - let { query } = this.getPersonNameQuery(dictionary.keyword.PerformingPhysicianName, value); + getPerformingPhysicianName(values) { + let query = this.getOrQuery(dictionary.keyword.PerformingPhysicianName, values, this.getPersonNameQuery.bind(this)); let includedPerformingPhysicianNameModel = this.getIncludedPerformingPhysicianNameModel(); if (!includedPerformingPhysicianNameModel) { this.includeQueries.push({ model: PersonNameModel, as: "performingPhysicianName", where: { - [Op.or]: [ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ] + [Op.or]: query[Op.or] }, attributes: [] }); - } else { - includedPerformingPhysicianNameModel.where[Op.or].push(...[ - { - alphabetic: query[Op.or].alphabetic - }, - { - ideographic: query[Op.or].ideographic - }, - { - phonetic: query[Op.or].phonetic - } - ]); } } - getOperatorsName(value) { - let { query } = this.getPersonNameQuery(dictionary.keyword.OperatorsName, value); + getOperatorsName(values) { + let query = this.getOrQuery(dictionary.keyword.OperatorsName, values, this.getPersonNameQuery.bind(this)); this.includeQueries.push({ model: PersonNameModel, as: "operatorsName", where: { - ...query + [Op.or]: query[Op.or] }, attributes: [] }); } - getSeriesNumber(value) { - let q = this.getStringQuery(dictionary.keyword.SeriesNumber, value); - this.query = { - ...this.query, - ...q - }; + getSeriesNumber(values) { + return this.getOrQuery(dictionary.keyword.SeriesNumber, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } @@ -150,55 +112,89 @@ class SeriesRequestAttributeSequence { this.seriesQueryBuilder = seriesQueryBuilder; } isModelIncluded() { - return this.seriesQueryBuilder.includeQueries.find(v=> v.model.getTableName() === "SeriesRequestAttributes"); - } - getAccessionNumber(value) { - let q = this.seriesQueryBuilder.getStringQuery("00080050", value); + return this.seriesQueryBuilder.includeQueries.find(v => v.model.getTableName() === "SeriesRequestAttributes"); + } + getAccessionNumber(values) { + let q = this.seriesQueryBuilder.getOrQuery( + dictionary.keyword.AccessionNumber, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) + ); this.addQuery(q); } - getIssuerLocalNameSpaceEntityID(value) { - let q = this.seriesQueryBuilder.getStringQuery("00080051_x00400031", value); + getIssuerLocalNameSpaceEntityID(values) { + let q = this.seriesQueryBuilder.getOrQuery( + "00080051_x00400031", + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) + ); this.addQuery(q); } - getIssuerUniversalEntityID(value) { - let q = this.seriesQueryBuilder.getStringQuery("00080051_x00400032", value); + getIssuerUniversalEntityID(values) { + let q = this.seriesQueryBuilder.getOrQuery( + "00080051_x00400032", + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) + ); this.addQuery(q); } - getIssuerUniversalEntityIDType(value) { - let q = this.seriesQueryBuilder.getStringQuery("00080051_x00400033", value); + getIssuerUniversalEntityIDType(values) { + let q = this.seriesQueryBuilder.getOrQuery( + "00080051_x00400033", + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) + ); this.addQuery(q); } - getRequestingService(value) { - let q = this.seriesQueryBuilder.getStringQuery("00321033", value); + /** + * + * @param {string[]} values - The values to be used in the query generation. + */ + getRequestingService(values) { + let q = this.seriesQueryBuilder.getOrQuery( + dictionary.keyword.RequestingService, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) + ); this.addQuery(q); } - getRequestedProcedureID(value) { - let q = this.seriesQueryBuilder.getStringQuery("00401001", value); + getRequestedProcedureID(values) { + let q = this.seriesQueryBuilder.getOrQuery( + dictionary.keyword.RequestedProcedureID, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) + ); this.addQuery(q); } - getStudyInstanceUID(value) { - let q = this.seriesQueryBuilder.getStringQuery("0020000D", value); + getStudyInstanceUID(values) { + let q = this.seriesQueryBuilder.getOrQuery( + dictionary.keyword.StudyInstanceUID, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) + ); this.addQuery(q); } addQuery(q) { let currentModel = this.isModelIncluded(); if (currentModel) { - currentModel.where = { - ...currentModel.where, - ...q - }; + currentModel.where[Op.and] = [ + ...currentModel.where[Op.and], + q + ]; } else { this.seriesQueryBuilder.includeQueries.push({ model: SeriesRequestAttributesModel, where: { - ...q + [Op.and]: [ + q + ] }, attributes: [] }); From 21750b6f44b010ffaf35cd913c1492d6e644948f Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Fri, 11 Aug 2023 15:46:26 +0800 Subject: [PATCH 061/365] feat: support build date time query --- .../QIDO-RS/service/querybuilder.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index d79f4247..428c50f3 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -259,10 +259,49 @@ class BaseQueryBuilder { } } + /** + * + * @param {string} tag + * @param {string} value + */ + getDateTimeQuery(tag, value) { + let dashIndex = value.indexOf("-"); + if (dashIndex === 0) { // -YYYYMMDD + return { + [`x${tag}`]: { + [Op.lte]: this.dateTimeStringToSqlDateTime(value.substring(1)) + } + }; + } else if (dashIndex === value.length - 1) { // YYYYMMDD- + return { + [`x${tag}`]: { + [Op.gte]: this.dateTimeStringToSqlDateTime(value.substring(0, dashIndex)) + } + }; + } else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD + return { + [`x${tag}`]: { + [Op.and]: [ + { [Op.gte]: this.dateTimeStringToSqlDateTime(value.substring(0, dashIndex)) }, + { [Op.lte]: this.dateTimeStringToSqlDateTime(value.substring(dashIndex + 1)) } + ] + } + }; + } else { // YYYYMMDD + return { + [`x${tag}`]: this.dateTimeStringToSqlDateTime(value) + }; + } + } + dateStringToSqlDateOnly(value) { return moment(value, "YYYYMMDD").format("YYYY-MM-DD"); } + dateTimeStringToSqlDateTime(value) { + return moment(value, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString(); + } + /** * * @param {string} timeStr From eb26522b66f67943ed37e7f19ee2709bccee70fc Mon Sep 17 00:00:00 2001 From: chin Date: Fri, 11 Aug 2023 23:02:44 +0800 Subject: [PATCH 062/365] feat: WADO-RS Retrieve Instance Resources APIs --- .../controller/WADO-RS/retrieveInstance.js | 84 ++++++ .../WADO-RS/retrieveStudy-Series-Instances.js | 80 ++++++ .../WADO-RS/retrieveStudyInstances.js | 80 ++++++ .../WADO-RS/service/WADO-RS.service.js | 255 ++++++++++++++++++ .../controller/WADO-RS/service/WADOZip.js | 110 ++++++++ api-sql/dicom-web/wado-rs-instance.route.js | 70 +++++ models/sql/models/instance.mode.js | 30 +++ models/sql/models/series.model.js | 22 ++ models/sql/models/study.model.js | 21 ++ routes.js | 2 +- 10 files changed, 753 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js create mode 100644 api-sql/dicom-web/wado-rs-instance.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js b/api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js new file mode 100644 index 00000000..e6fb18cd --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js @@ -0,0 +1,84 @@ +const wadoService = require("./service/WADO-RS.service"); +const { WADOZip } = require("./service/WADOZip"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); +class RetrieveInstanceOfSeriesOfStudiesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); + apiLogger.logger.info(`Request Accept: ${this.request.headers.accept}`); + + try { + + if (this.request.headers.accept.toLowerCase() === "application/zip") { + return await this.responseZip(); + } else if (this.request.headers.accept.includes("multipart/related")) { + return await this.responseMultipartRelated(); + } else if (this.request.headers.accept.includes("*")){ + this.request.headers.accept = "multipart/related; type=\"application/dicom\""; + return await this.responseMultipartRelated(); + } + + return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); + } catch(e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } + + async responseZip() { + let wadoZip = new WADOZip(this.request.params, this.response); + let zipResult = await wadoZip.getZipOfInstanceDICOMFile(); + if (zipResult.status) { + return this.response.end(); + } else { + this.response.writeHead(zipResult.code, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(zipResult)); + } + } + + async responseMultipartRelated() { + let type = wadoService.getAcceptType(this.request); + let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; + if (!isSupported) { + return wadoService.sendNotSupportedMediaType(this.response, type); + } + + let imageMultipartWriter = new wadoService.ImageMultipartWriter( + this.request, + this.response, + wadoService.InstanceImagePathFactory, + wadoService.multipartContentTypeWriter[type] + ); + + return await imageMultipartWriter.write(); + } +} + + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function(req, res) { + let controller = new RetrieveInstanceOfSeriesOfStudiesController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js b/api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js new file mode 100644 index 00000000..bd5a2d6d --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js @@ -0,0 +1,80 @@ +const { logger } = require("@root/utils/logs/log"); +const wadoService = require("./service/WADO-RS.service"); +const { WADOZip } = require("./service/WADOZip"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { Controller } = require("@root/api/controller.class"); + +class RetrieveInstancesOfSeries extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + try { + logger.info(`[WADO-RS] [Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}] [Request Accept: ${this.request.headers.accept}]`); + + if (this.request.headers.accept.toLowerCase() === "application/zip") { + return await this.responseZip(); + } else if (this.request.headers.accept.includes("multipart/related")) { + return await this.responseMultipartRelated(); + } else if (this.request.headers.accept.includes("*")){ + this.request.headers.accept = "multipart/related; type=\"application/dicom\""; + return await this.responseMultipartRelated(); + } + + return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + logger.error(`[WADO-RS] [Error: ${errorStr}]`); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } + + async responseZip() { + let wadoZip = new WADOZip(this.request.params, this.response); + let zipResult = await wadoZip.getZipOfSeriesDICOMFiles(); + if (zipResult.status) { + return this.response.end(); + } else { + this.response.writeHead(zipResult.code, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(zipResult)); + } + } + + async responseMultipartRelated() { + let type = wadoService.getAcceptType(this.request); + let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; + if (!isSupported) { + return wadoService.sendNotSupportedMediaType(this.response, type); + } + + let imageMultipartWriter = new wadoService.ImageMultipartWriter( + this.request, + this.response, + wadoService.SeriesImagePathFactory, + wadoService.multipartContentTypeWriter[type] + ); + + return await imageMultipartWriter.write(); + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function (req, res) { + let controller = new RetrieveInstancesOfSeries(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js b/api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js new file mode 100644 index 00000000..3ad67fef --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js @@ -0,0 +1,80 @@ +const { logger } = require("@root/utils/logs/log"); +const wadoService = require("./service/WADO-RS.service"); +const { WADOZip } = require("./service/WADOZip"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { Controller } = require("@root/api/controller.class"); + +class RetrieveStudyInstancesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + try { + logger.info(`[WADO-RS] [Get study's instances, study UID: ${this.request.params.studyUID}] [Request Accept: ${this.request.headers.accept}]`); + + if (this.request.headers.accept.toLowerCase() === "application/zip") { + return await this.responseZip(); + } else if (this.request.headers.accept.includes("multipart/related")) { + return await this.responseMultipartRelated(); + } else if (this.request.headers.accept.includes("*")) { + this.request.headers.accept = "multipart/related; type=\"application/dicom\""; + return await this.responseMultipartRelated(); + } + + return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + logger.error(`[WADO-RS] [Error: ${errorStr}]`); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); + } + } + + async responseZip() { + let wadoZip = new WADOZip(this.request.params, this.response); + let zipResult = await wadoZip.getZipOfStudyDICOMFiles(); + if (zipResult.status) { + return this.response.end(); + } else { + this.response.writeHead(zipResult.code, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(zipResult)); + } + } + + async responseMultipartRelated() { + let type = wadoService.getAcceptType(this.request); + let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; + if (!isSupported) { + return wadoService.sendNotSupportedMediaType(this.response, type); + } + + let imageMultipartWriter = new wadoService.ImageMultipartWriter( + this.request, + this.response, + wadoService.StudyImagePathFactory, + wadoService.multipartContentTypeWriter[type] + ); + + return await imageMultipartWriter.write(); + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function (req, res) { + let controller = new RetrieveStudyInstancesController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js new file mode 100644 index 00000000..6841ebb3 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -0,0 +1,255 @@ +const _ = require("lodash"); +const path = require("path"); +const fsP = require("fs/promises"); +const { MultipartWriter } = require("@root/utils/multipartWriter"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { raccoonConfig } = require("@root/config-class"); +const { JSONPath } = require("jsonpath-plus"); +const { DicomWebService } = require("@root/api/dicom-web/service/dicom-web.service"); +const { logger } = require("@root/utils/logs/log"); +const { InstanceModel } = require("@models/sql/models/instance.mode"); +const { StudyModel } = require("@models/sql/models/study.model"); +const { SeriesModel } = require("@models/sql/models/series.model"); + +/** + * + * @param {import("http").IncomingMessage} req + * @return { string } + */ +function getAcceptType(req) { + return req.headers.accept + .match(/type=(.*)/gi)[0] + .split(/[,;]/)[0] + .substring(5) + .replace(/"/g, ""); +} + +class ImageMultipartWriter { + /** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {typeof ImagePathFactory} imagePathFactory + * @param {typeof ContentTypeWriter} contentTypeWriter + */ + constructor(req, res, imagePathFactory, contentTypeWriter) { + this.request = req; + this.response = res; + this.imagePathFactory = new imagePathFactory(req.params); + this.contentTypeWriterClass = contentTypeWriter; + } + + async write() { + await this.imagePathFactory.getImagePaths(); + let checkAllImageExistResult = await this.imagePathFactory.checkAllImageExist(); + this.response.statusCode = checkAllImageExistResult.code; + if (!checkAllImageExistResult.status) { + this.response.setHeader("Content-Type", "application/dicom+json"); + return this.response.json(checkAllImageExistResult); + } + logger.info(`retrieve study's images: ${this.imagePathFactory.getPartialImagesPathString()}`); + + /** @type { ContentTypeWriter } */ + let contentTypeWriter = new this.contentTypeWriterClass( + this.imagePathFactory.imagePaths, + this.request, + this.response + ); + let writeResult = await contentTypeWriter.write(); + if (!writeResult.status) { + this.response.setHeader("Content-Type", "application/dicom+json"); + return this.response.status(writeResult.code).json(writeResult); + } + + return this.response.end(); + } +} + + +class ImagePathFactory { + + /** + * + * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + */ + constructor(uids) { + /** @type { import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[] } */ + this.imagePaths = []; + /** @type {Uids} */ + this.uids = uids; + } + + async getImagePaths() { } + + async checkAllImageExist() { + if (this.imagePaths.length === 0) { + return { + status: false, + code: 404, + message: `not found, ${this.getUidsString()}` + }; + } + + let existArr = await this.getImageExistArray(); + if (existArr.every(v => v)) { + return { + status: true, + code: 200 + }; + } else if (existArr.some(v => v)) { + return { + status: true, + code: 206 + }; + } else { + return { + status: false, + code: 410, + message: "Images gone, but data exist" + }; + } + } + + async getImageExistArray() { + /** @type {boolean[]} */ + let existArr = []; + let imagePathsClone = _.cloneDeep(this.imagePaths); + for (let i = 0; i < imagePathsClone.length; i++) { + let imagePathObj = imagePathsClone[i]; + try { + await fsP.access(imagePathObj.instancePath, fsP.constants.F_OK); + existArr.push(true); + } catch (e) { + this.imagePaths.splice(i, 1); + existArr.push(false); + } + } + return existArr; + } + + getUidsString() { + let uidsKeys = Object.keys(this.uids); + let strArr = []; + for (let i = 0; i < uidsKeys.length; i++) { + let key = uidsKeys[i]; + strArr.push(`${key}: ${this.uids[key]}`); + } + return strArr.join(", "); + } + + getPartialImagesPathString() { + return JSON.stringify(this.imagePaths.slice(0, 10).map(v => v.instancePath)); + } +} + +class StudyImagePathFactory extends ImagePathFactory { + constructor(uids) { + super(uids); + } + + async getImagePaths() { + this.imagePaths = await StudyModel.getPathGroupOfInstances(this.uids); + } +} + +class SeriesImagePathFactory extends ImagePathFactory { + constructor(uids) { + super(uids); + } + + async getImagePaths() { + this.imagePaths = await SeriesModel.getPathGroupOfInstances(this.uids); + } +} + +class InstanceImagePathFactory extends ImagePathFactory { + constructor(uids) { + super(uids); + } + + async getImagePaths() { + let imagePath = await InstanceModel.getPathOfInstance(this.uids); + + if(imagePath) + this.imagePaths = [imagePath]; + else + this.imagePaths = []; + } +} + +class ContentTypeWriter { + constructor(imagePaths, req, res) { + this.imagePaths = imagePaths; + this.request = req; + this.response = res; + } + + /** + * For image pre-processing, such as converting to compressed bulk data (i.e. image/jpeg, image/dicom-rle, etc.) + */ + async preprocess() { } + + async write() { } +} + +class DicomTypeWriter extends ContentTypeWriter { + constructor(imagePaths, req, res) { + super(imagePaths, req, res); + } + + async preprocess() { } + + async write() { + let multipartWriter = new MultipartWriter(this.imagePaths, this.request, this.response); + return await multipartWriter.writeDICOMFiles("application/dicom"); + } +} + +const multipartContentTypeWriter = { + "application/dicom": DicomTypeWriter, + "application/octet-stream": DicomTypeWriter +}; + +const supportInstanceMultipartType = ["application/dicom", "application/octet-stream"]; + +/** + * + * @param { import('express').Response } res + * @param { string } type + * @returns + */ +function sendNotSupportedMediaType(res, type) { + let errorMessage = errorResponse.getNotSupportedErrorMessage(`The type ${type} is not supported, server supported \`multipart/related; type="application/dicom"\`, \`multipart/related; type="application/octet-stream"\` and \`application/zip\``); + res.writeHead(errorMessage.HttpStatus, { + "Content-Type": "application/dicom+json" + }); + return res.end(JSON.stringify(errorMessage)); +} + +function addHostnameOfBulkDataUrl(metadata, req) { + let dicomWebService = new DicomWebService(req, undefined); + + let urItems = JSONPath({ + path: "$..BulkDataURI", + json: metadata, + resultType: "all" + }); + + for (let urItem of urItems) { + let bulkDataUriPath = JSONPath.toPathArray(urItem.path).join(".").substring(2); + let relativeUrl = _.get(metadata, bulkDataUriPath); + + _.set(metadata, bulkDataUriPath, `${dicomWebService.getBasicURL()}${relativeUrl}`); + } +} + +module.exports.getAcceptType = getAcceptType; +module.exports.supportInstanceMultipartType = supportInstanceMultipartType; +module.exports.sendNotSupportedMediaType = sendNotSupportedMediaType; +module.exports.addHostnameOfBulkDataUrl = addHostnameOfBulkDataUrl; +module.exports.ImagePathFactory = ImagePathFactory; +module.exports.StudyImagePathFactory = StudyImagePathFactory; +module.exports.SeriesImagePathFactory = SeriesImagePathFactory; +module.exports.InstanceImagePathFactory = InstanceImagePathFactory; +module.exports.multipartContentTypeWriter = multipartContentTypeWriter; +module.exports.ImageMultipartWriter = ImageMultipartWriter; diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js b/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js new file mode 100644 index 00000000..ca7d2f11 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js @@ -0,0 +1,110 @@ +const archiver = require("archiver"); +const wadoService = require("./WADO-RS.service"); +const path = require("path"); +const { StudyModel } = require("@models/sql/models/study.model"); +const { SeriesModel } = require("@models/sql/models/series.model"); +const { InstanceModel } = require("@models/sql/models/instance.mode"); + +class WADOZip { + constructor(iParam, iRes) { + this.requestParams = iParam; + this.studyUID = iParam.studyUID; + this.seriesUID = iParam.seriesUID; + this.instanceUID = iParam.instanceUID; + this.res = iRes; + } + + setHeaders(uid) { + this.res.attachment = `${uid}.zip`; + this.res.setHeader('Content-Type', 'application/zip'); + this.res.setHeader('Content-Disposition', `attachment; filename=${uid}.zip`); + } + + async getZipOfStudyDICOMFiles() { + let imagesPathList = await StudyModel.getPathGroupOfInstances(this.requestParams); + if (imagesPathList.length > 0) { + this.setHeaders(this.studyUID); + + let folders = []; + for (let i = 0; i < imagesPathList.length; i++) { + let imagesFolder = path.dirname(imagesPathList[i].instancePath); + if (!folders.includes(imagesFolder)) { + folders.push(imagesFolder); + } + } + return await toZip(this.res, folders); + } + return { + status: false, + code: 404, + message: `not found, Study UID: ${this.requestParams.studyUID}` + }; + } + + async getZipOfSeriesDICOMFiles() { + let imagesPathList = await SeriesModel.getPathGroupOfInstances(this.requestParams); + if (imagesPathList.length > 0) { + this.setHeaders(this.seriesUID); + + let folders = []; + let seriesPath = path.dirname(imagesPathList[0].instancePath); + folders.push(seriesPath); + return await toZip(this.res, folders); + } + return { + status: false, + code: 404, + message: `not found, Series UID: ${this.requestParams.seriesUID}, Study UID: ${this.requestParams.studyUID}` + }; + } + + async getZipOfInstanceDICOMFile() { + let imagePath = await InstanceModel.getPathOfInstance(this.requestParams); + if (imagePath) { + this.setHeaders(this.instanceUID); + + return await toZip(this.res, [], imagePath.instancePath); + } + return { + status: false, + code: 404, + message: `not found, Instance UID: ${this.requestParams.instanceUID}, Series UID: ${this.requestParams.seriesUID}, Study UID: ${this.requestParams.studyUID}` + }; + } +} + +function toZip(res, folders = [], filename = "") { + return new Promise((resolve) => { + let archive = archiver('zip', { + gzip: true, + zlib: { level: 9 } // Sets the compression level. + }); + archive.on('error', function (err) { + console.error(err); + resolve({ + status: false, + code: 500, + data: err + }); + }); + archive.pipe(res); + if (folders.length > 0) { + for (let i = 0; i < folders.length; i++) { + let folderName = path.basename(folders[i]); + //archive.append(null, {name : folderName}); + archive.glob("*.dcm", { cwd: folders[i] }, { prefix: folderName }); + } + } else { + archive.file(filename); + } + archive.finalize().then(() => { + resolve({ + status: true, + code: 200, + data: "success" + }); + }); + }); +} + +module.exports.WADOZip = WADOZip; diff --git a/api-sql/dicom-web/wado-rs-instance.route.js b/api-sql/dicom-web/wado-rs-instance.route.js new file mode 100644 index 00000000..c3655977 --- /dev/null +++ b/api-sql/dicom-web/wado-rs-instance.route.js @@ -0,0 +1,70 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi } = require("@root/api/validator"); +const router = express(); + +//#region WADO-RS Retrieve Transaction Instance Resources + +/** + * @openapi + * /dicom-web/studies/{studyUID}: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's instances + * parameters: + * - $ref: "#/components/parameters/studyUID" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedDICOM" + * + */ +router.get( + "/studies/:studyUID", + require("./controller/WADO-RS/retrieveStudyInstances") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's series' instances + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedDICOM" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID", + require("./controller/WADO-RS/retrieveStudy-Series-Instances") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's instances + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedDICOM" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID", + require("./controller/WADO-RS/retrieveInstance") +); + +//#endregion + +module.exports = router; \ No newline at end of file diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.mode.js index e3e10828..0f782d43 100644 --- a/models/sql/models/instance.mode.js +++ b/models/sql/models/instance.mode.js @@ -4,6 +4,7 @@ const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { getStoreDicomFullPath } = require("@models/mongodb/service"); class InstanceModel extends Model { }; @@ -79,4 +80,33 @@ InstanceModel.getDicomJson = async function(queryOptions) { })); }; +InstanceModel.getPathOfInstance = async function(iParam) { + let { studyUID, seriesUID, instanceUID } = iParam; + + try { + let instance = await sequelizeInstance.model("Instance").findOne({ + where: { + x0020000D: studyUID, + x0020000E: seriesUID, + x00080018: instanceUID + } + }); + + if (instance) { + let instanceJson = await instance.toJSON(); + + _.set(instanceJson, "instancePath", getStoreDicomFullPath(instanceJson)); + _.set(instanceJson, "studyUID", instanceJson.x0020000D); + _.set(instanceJson, "seriesUID", instanceJson.x0020000E); + _.set(instanceJson, "instanceUID", instanceJson.x00080018); + + return instanceJson; + } + + return undefined; + } catch(e) { + throw e; + } +}; + module.exports.InstanceModel = InstanceModel; diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index a2c5465c..8959abaf 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -4,6 +4,7 @@ const { vrTypeMapping } = require("../vrTypeMapping"); const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder"); const _ = require("lodash"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); class SeriesModel extends Model { }; @@ -101,4 +102,25 @@ SeriesModel.getDicomJson = async function(queryOptions) { })); }; +SeriesModel.getPathGroupOfInstances = async function(iParam) { + let { studyUID, seriesUID } = iParam; + + try { + let instances = await sequelizeInstance.model("Instance").findAll({ + where: { + x0020000D: studyUID, + x0020000E: seriesUID + }, + attributes: ["instancePath"] + }); + + let fullPathGroup = getStoreDicomFullPathGroup(instances); + + return fullPathGroup; + + } catch (e) { + throw e; + } +}; + module.exports.SeriesModel = SeriesModel; diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index ae49f005..e343da3c 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -6,6 +6,7 @@ const _ = require("lodash"); const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder"); const { InstanceModel } = require("./instance.mode"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); class StudyModel extends Model { async getNumberOfStudyRelatedSeries() { @@ -148,4 +149,24 @@ StudyModel.getDicomJson = async function (queryOptions) { })); }; +StudyModel.getPathGroupOfInstances = async function(iParam) { + let { studyUID } = iParam; + + try { + let instances = await sequelizeInstance.model("Instance").findAll({ + where: { + x0020000D: studyUID + }, + attributes: ["instancePath"] + }); + + let fullPathGroup = getStoreDicomFullPathGroup(instances); + + return fullPathGroup; + + } catch (e) { + throw e; + } +}; + module.exports.StudyModel = StudyModel; diff --git a/routes.js b/routes.js index 7328bc31..df0fa3d2 100644 --- a/routes.js +++ b/routes.js @@ -21,7 +21,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/stow-rs.route")); app.use("/dicom-web", require("./api-sql/dicom-web/qido-rs.route")); - app.use("/dicom-web", require("./api/dicom-web/wado-rs-instance.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); From f20121961cdba807f7c3aad6fcea68736fcdb3e7 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Mon, 14 Aug 2023 14:40:07 +0800 Subject: [PATCH 063/365] fix: typo --- api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js | 2 +- api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js | 2 +- api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js | 2 +- models/sql/init.js | 2 +- models/sql/models/{instance.mode.js => instance.model.js} | 0 models/sql/models/study.model.js | 2 +- models/sql/po/instance.po.js | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename models/sql/models/{instance.mode.js => instance.model.js} (100%) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 461158bb..fdedc2eb 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -10,7 +10,7 @@ const { } = require("@error/dicom-web-service"); const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); -const { InstanceModel } = require("@models/sql/models/instance.mode"); +const { InstanceModel } = require("@models/sql/models/instance.model"); const { PatientModel } = require("@models/sql/models/patient.model"); diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 6841ebb3..06d39d99 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -7,7 +7,7 @@ const { raccoonConfig } = require("@root/config-class"); const { JSONPath } = require("jsonpath-plus"); const { DicomWebService } = require("@root/api/dicom-web/service/dicom-web.service"); const { logger } = require("@root/utils/logs/log"); -const { InstanceModel } = require("@models/sql/models/instance.mode"); +const { InstanceModel } = require("@models/sql/models/instance.model"); const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js b/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js index ca7d2f11..1fd58baf 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js @@ -3,7 +3,7 @@ const wadoService = require("./WADO-RS.service"); const path = require("path"); const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); -const { InstanceModel } = require("@models/sql/models/instance.mode"); +const { InstanceModel } = require("@models/sql/models/instance.model"); class WADOZip { constructor(iParam, iRes) { diff --git a/models/sql/init.js b/models/sql/init.js index e9b957b3..cb9cde87 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -2,7 +2,7 @@ const { PersonNameModel } = require("./models/personName.model"); const { PatientModel } = require("./models/patient.model"); const { StudyModel } = require("./models/study.model"); const { SeriesModel } = require("./models/series.model"); -const { InstanceModel } = require("./models/instance.mode"); +const { InstanceModel } = require("./models/instance.model"); const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); const { raccoonConfig } = require("@root/config-class"); diff --git a/models/sql/models/instance.mode.js b/models/sql/models/instance.model.js similarity index 100% rename from models/sql/models/instance.mode.js rename to models/sql/models/instance.model.js diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index e343da3c..d21adf07 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -4,7 +4,7 @@ const { vrTypeMapping } = require("../vrTypeMapping"); const { SeriesModel } = require("./series.model"); const _ = require("lodash"); const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder"); -const { InstanceModel } = require("./instance.mode"); +const { InstanceModel } = require("./instance.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 71be7139..490daa13 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -1,7 +1,7 @@ const moment = require("moment"); const _ = require("lodash"); const { PersonNameModel } = require("../models/personName.model"); -const { InstanceModel } = require("../models/instance.mode"); +const { InstanceModel } = require("../models/instance.model"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); const { DicomCodeModel } = require("../models/dicomCode.model"); From c21c55e259692248e418d7f876ccb157bacebf40 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Mon, 14 Aug 2023 16:24:19 +0800 Subject: [PATCH 064/365] feat: Retrieve Transaction Rendered Resources APIs --- .../WADO-RS/rendered/instanceFrames.js | 74 +++++++ .../controller/WADO-RS/rendered/instances.js | 61 ++++++ .../controller/WADO-RS/rendered/series.js | 56 +++++ .../controller/WADO-RS/rendered/study.js | 65 ++++++ .../WADO-RS/service/rendered.service.js | 202 ++++++++++++++++++ api-sql/dicom-web/wado-rs-rendered.route.js | 146 +++++++++++++ models/sql/models/instance.model.js | 22 ++ models/sql/models/series.model.js | 9 +- models/sql/models/study.model.js | 9 +- models/sql/po/instance.po.js | 8 + routes.js | 2 +- 11 files changed, 649 insertions(+), 5 deletions(-) create mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/instances.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/series.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/study.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js create mode 100644 api-sql/dicom-web/wado-rs-rendered.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js b/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js new file mode 100644 index 00000000..f059709b --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js @@ -0,0 +1,74 @@ +const _ = require("lodash"); +const renderedService = require("../service/rendered.service"); +const { + RenderedImageMultipartWriter +} = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); +const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class RetrieveRenderedInstanceFramesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + this.apiLogger = new ApiLogger(this.request, "WADO-RS"); + this.apiLogger.addTokenValue(); + + let { + studyUID, + seriesUID, + instanceUID, + frameNumber + } = this.request.params; + + this.apiLogger.logger.info(`Get study's series' rendered instances' frames, study UID: ${studyUID}, series UID: ${seriesUID}, instance UID: ${instanceUID}, frame: ${frameNumber}`); + + let headerAccept = _.get(this.request.headers, "accept", ""); + if (!headerAccept.includes("*/*") && !headerAccept.includes("image/jpeg")) { + let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow */* or image/jpeg , exception : ${headerAccept}`); + this.response.writeHead(badRequestMessage.HttpStatus, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(badRequestMessage)); + } + + try { + let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + this.request, + this.response, + InstanceImagePathFactory, + renderedService.InstanceFramesListWriter + ); + + let buffer = await renderedImageMultipartWriter.write(); + + this.apiLogger.logger.info(`Get instance's frame successfully, instance UID: ${instanceUID}, frame number: ${JSON.stringify(frameNumber)}`); + + if (buffer instanceof Buffer) { + return this.response.end(buffer, "binary"); + } + + return this.response.end(); + } catch(e) { + console.error(e); + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify(e, Object.getOwnPropertyNames(e), 4), "utf8"); + } + } +} +/** + * + * @param {import("http").incomingMessage} req + * @param {import("http").ServerResponse} res + * @returns + */ +module.exports = async function(req, res) { + let controller = new RetrieveRenderedInstanceFramesController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js b/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js new file mode 100644 index 00000000..a4a8f443 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js @@ -0,0 +1,61 @@ +const _ = require("lodash"); +const { RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); +const renderedService = require("../service/rendered.service"); +const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class RetrieveRenderedInstancesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + let headerAccept = _.get(this.request.headers, "accept", ""); + + apiLogger.logger.info(`Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); + + if (!headerAccept == `multipart/related; type="image/jpeg"`) { + let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); + this.response.writeHead(badRequestMessage.HttpStatus, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(badRequestMessage)); + } + + try { + let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + this.request, + this.response, + InstanceImagePathFactory, + renderedService.InstanceFramesWriter + ); + + await renderedImageMultipartWriter.write(); + + apiLogger.logger.info(`Write Multipart Successfully, study's series' instances' rendered images, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}, instance UID: ${this.request.params.instanceUID}`); + return this.response.end(); + } catch(e) { + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify(e)); + } + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + * @returns + */ +module.exports = async function(req, res) { + let controller = new RetrieveRenderedInstancesController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/series.js b/api-sql/dicom-web/controller/WADO-RS/rendered/series.js new file mode 100644 index 00000000..d037cc3e --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/series.js @@ -0,0 +1,56 @@ +const _ = require("lodash"); +const { RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); +const renderedService = require("../service/rendered.service"); +const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { logger } = require("@root/utils/logs/log"); +const { Controller } = require("@root/api/controller.class"); + +class RetrieveRenderedSeriesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let headerAccept = _.get(this.request.headers, "accept", ""); + logger.info(`[WADO-RS] [Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); + if (!headerAccept == `multipart/related; type="image/jpeg"`) { + let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); + this.response.writeHead(badRequestMessage.HttpStatus, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(badRequestMessage)); + } + + try { + let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + this.request, + this.response, + SeriesImagePathFactory, + renderedService.SeriesFramesWriter + ); + + await renderedImageMultipartWriter.write(); + + logger.info(`[WADO-RS] [path: ${this.request.originalUrl}] [Write Multipart Successfully, study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); + + return this.response.end(); + } catch(e) { + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify(e)); + } + } +} +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + * @returns + */ +module.exports = async function(req, res) { + let controller = new RetrieveRenderedSeriesController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/study.js b/api-sql/dicom-web/controller/WADO-RS/rendered/study.js new file mode 100644 index 00000000..630cf528 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/study.js @@ -0,0 +1,65 @@ +const _ = require("lodash"); +const { RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); +const renderedService = require("../service/rendered.service"); +const { + StudyImagePathFactory +} = require("../service/WADO-RS.service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { Controller } = require("@root/api/controller.class"); + +class RetrieveRenderedStudyController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let headerAccept = _.get(this.request.headers, "accept", ""); + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get study's rendered instances, study UID: ${this.request.params.studyUID}`); + + if (!headerAccept == `multipart/related; type="image/jpeg"`) { + let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); + this.response.writeHead(badRequestMessage.HttpStatus, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(badRequestMessage)); + } + + try { + + let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + this.request, + this.response, + StudyImagePathFactory, + renderedService.StudyFramesWriter + ); + + await renderedImageMultipartWriter.write(); + + apiLogger.logger.info(`Write Multipart Successfully, study's rendered instances, study UID: ${this.request.params.studyUID}`); + + return this.response.end(); + } catch(e) { + apiLogger.logger.error(e); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify(e)); + } + } +} +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + * @returns + */ +module.exports = async function(req, res) { + let controller = new RetrieveRenderedStudyController(req, res); + + await controller.doPipeline(); +}; diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js new file mode 100644 index 00000000..98de69e4 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -0,0 +1,202 @@ +const { + postProcessFrameImage, + writeRenderedImages, + writeSpecificFramesRenderedImages +} = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); + +const path = require("path"); +const _ = require("lodash"); +const { MultipartWriter } = require("@root/utils/multipartWriter"); +const notImageSOPClass = require("@models/DICOM/dicomWEB/notImageSOPClass"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { logger } = require("@root/utils/logs/log"); + +const { raccoonConfig } = require("@root/config-class"); +const { Op } = require("sequelize"); +const { InstanceModel } = require("@models/sql/models/instance.model.js"); + +class FramesWriter { + /** + * + * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[]} imagePaths + */ + constructor(req, res, imagePaths) { + this.request = req; + this.response = res; + this.imagePaths = imagePaths; + } + + async write() { + let multipartWriter = new MultipartWriter([], this.request, this.response); + for (let imagePathObj of this.imagePaths) { + let instanceFramesObj = await getInstanceFrameObj(imagePathObj); + if (_.isUndefined(instanceFramesObj)) continue; + let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); + dicomNumberOfFrames = parseInt(dicomNumberOfFrames); + await writeRenderedImages(this.request, dicomNumberOfFrames, instanceFramesObj, multipartWriter); + } + multipartWriter.writeFinalBoundary(); + } +} + +class StudyFramesWriter extends FramesWriter { + /** + * + * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[]} imagePaths + */ + constructor(req, res, imagePaths) { + super(req, res, imagePaths); + } +} + +class SeriesFramesWriter extends FramesWriter { + constructor(req, res, imagePaths) { + super(req, res, imagePaths); + } +} + +class InstanceFramesWriter extends FramesWriter { + constructor(req, res, imagePaths) { + super(req, res, imagePaths); + } + + async write() { + let multipartWriter = new MultipartWriter([], this.request, this.response); + let instanceFramesObj = await getInstanceFrameObj(this.imagePaths[0]); + if (_.isUndefined(instanceFramesObj)) { + return this.response.status(400).json( + errorResponse.getBadRequestErrorMessage(`instance: ${this.request.params.instanceUID} doesn't have pixel data`) + ); + } + let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); + dicomNumberOfFrames = parseInt(dicomNumberOfFrames); + await writeRenderedImages(this.request, dicomNumberOfFrames, instanceFramesObj, multipartWriter); + multipartWriter.writeFinalBoundary(); + } +} + +class InstanceFramesListWriter extends FramesWriter { + constructor(req, res, imagePaths) { + super(req, res, imagePaths); + this.instanceFramesObj = {}; + this.dicomNumberOfFrames = 1; + } + + async write() { + let { frameNumber } = this.request.params; + + this.instanceFramesObj = await getInstanceFrameObj(this.imagePaths[0]); + if (_.isUndefined(this.instanceFramesObj)) { + return this.response.status(400).json( + errorResponse.getBadRequestErrorMessage(`instance: ${this.request.params.instanceUID} doesn't have pixel data`) + ); + } + this.dicomNumberOfFrames = _.get(this.instanceFramesObj, "x00280008", 1); + this.dicomNumberOfFrames = parseInt(this.dicomNumberOfFrames); + + if (this.isInvalidFrameNumber()) return; + + if (frameNumber.length == 1) { + return this.writeSingleFrame(); + } else { + let multipartWriter = new MultipartWriter([], this.request, this.response); + await writeSpecificFramesRenderedImages(this.request, frameNumber, this.instanceFramesObj, multipartWriter); + multipartWriter.writeFinalBoundary(); + return true; + } + } + + isInvalidFrameNumber() { + for (let i = 0; i < this.request.params.frameNumber.length; i++) { + let frame = this.request.params.frameNumber[i]; + if (frame > this.dicomNumberOfFrames) { + let badRequestMessage = errorResponse.getBadRequestErrorMessage(`Bad frame number , \ +This instance NumberOfFrames is : ${this.dicomNumberOfFrames} , But request ${JSON.stringify(this.request.params.frameNumber)}`); + this.response.writeHead(badRequestMessage.HttpStatus, { + "Content-Type": "application/dicom+json" + }); + + let badRequestMessageStr = JSON.stringify(badRequestMessage); + + logger.warn(badRequestMessageStr); + + return this.response.end(JSON.stringify(badRequestMessageStr)); + } + } + return false; + } + + async writeSingleFrame() { + let postProcessResult = await postProcessFrameImage(this.request, this.request.params.frameNumber[0], this.instanceFramesObj); + if (postProcessResult.status) { + this.response.writeHead(200, { + "Content-Type": "image/jpeg" + }); + + return postProcessResult.magick.toBuffer(); + } + throw new Error(`Can not process this image, instanceUID: ${this.instanceFramesObj.instanceUID}, frameNumber: ${this.request.frameNumber[0]}`); + } +} + +/** + * SQL 的彈性比較低,此 function 採與 MongoDB 相同呼叫方式,但在欄位設計上較死,otherFields 無用處 + * + * SQL has lower flexibility compared to MongoDB. + * This function adopts the same calling method as MongoDB, but it is more rigid in terms of field design. + * Note that otherFields is not used. + * @param {Object} iParam + * @return { Promise | Promise } + */ +async function getInstanceFrameObj(iParam, otherFields = {}) { + let { studyUID, seriesUID, instanceUID } = iParam; + try { + /** @type { import("sequelize").FindOptions } */ + let query = { + where: { + x0020000D: studyUID, + x0020000E: seriesUID, + x00080018: instanceUID, + x00080016: { + [Op.notIn]: notImageSOPClass + } + }, + attributes: [ + "instancePath", + "x00020010", + "x0020000D", + "x0020000E", + "x00080018", + "x00280008", + "x00281050", + "x00281051" + ] + }; + + let instance = await InstanceModel.findOne(query); + if (instance) { + let instanceJson = instance.toJSON(); + + _.set(instanceJson, "studyUID", instanceJson.x0020000D); + _.set(instanceJson, "seriesUID", instanceJson.x0020000E); + _.set(instanceJson, "instanceUID", instanceJson.x00080018); + _.set(instanceJson, "instancePath", path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + instanceJson.instancePath + )); + + return instanceJson; + } + + return undefined; + } catch (e) { + throw e; + } +} + + +module.exports.getInstanceFrameObj = getInstanceFrameObj; +module.exports.StudyFramesWriter = StudyFramesWriter; +module.exports.SeriesFramesWriter = SeriesFramesWriter; +module.exports.InstanceFramesWriter = InstanceFramesWriter; +module.exports.InstanceFramesListWriter = InstanceFramesListWriter; \ No newline at end of file diff --git a/api-sql/dicom-web/wado-rs-rendered.route.js b/api-sql/dicom-web/wado-rs-rendered.route.js new file mode 100644 index 00000000..ff01ef88 --- /dev/null +++ b/api-sql/dicom-web/wado-rs-rendered.route.js @@ -0,0 +1,146 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi } = require("@root/api/validator"); +const router = express(); + + +//#region WADO-RS Retrieve Transaction Rendered Resources + +const renderedQueryValidation = { + quality: Joi.number().integer().min(1).max(100), + iccprofile: Joi.string().default("no").valid("no", "yes", "srgb", "adobergb", "rommrgb"), + viewport: Joi.string().custom((v, helper) => { + let valueSplit = v.split(","); + if (valueSplit.length == 2) { + let [vw, vh] = valueSplit; + if (!Joi.number().min(0).validate(vw).error && + !Joi.number().min(0).validate(vh).error) { + return v; + } + return helper.message(`invalid viewport parameter, viewport=vw,vh. The vw and vh must be number`); + } else if (valueSplit.length == 6) { + let [vw, vh, sx, sy, sw, sh] = valueSplit; + if (Joi.number().empty("").validate(sx).error) { + return helper.message("invalid viewport parameter, sx must be number"); + } else if (Joi.number().empty("").validate(sy).error) { + return helper.message("invalid viewport parameter, sy must be number"); + } + [vw, vh, sx, sy, sw, sh] = valueSplit.map(v=> Number(v)); + if (!Joi.number().min(0).validate(vw).error && + !Joi.number().min(0).validate(vh).error && + !Joi.number().min(0).validate(sx).error && + !Joi.number().min(0).validate(sy).error && + !Joi.number().validate(sw).error && + !Joi.number().validate(sh).error + ) { + return v; + } + } + return helper.message("invalid viewport parameter, viewport=vw,vh or viewport=vw,vh,sx,sy,sw,sh"); + }) +}; + +/** + * @openapi + * /dicom-web/studies/{studyUID}/rendered: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's rendered images + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/quality" + * - $ref: "#/components/parameters/viewport" + * - $ref: "#/components/parameters/iccprofile" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedImageJpeg" + * + */ +router.get( + "/studies/:studyUID/rendered", + validateParams(renderedQueryValidation, "query", { allowUnknown: false }), + require("./controller/WADO-RS/rendered/study") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/rendered: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's Series' rendered images + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/quality" + * - $ref: "#/components/parameters/viewport" + * - $ref: "#/components/parameters/iccprofile" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedImageJpeg" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/rendered", + validateParams(renderedQueryValidation, "query", { allowUnknown: false }), + require("./controller/WADO-RS/rendered/series") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/rendered: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's Series' instance's rendered images + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * - $ref: "#/components/parameters/quality" + * - $ref: "#/components/parameters/viewport" + * - $ref: "#/components/parameters/iccprofile" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedImageJpeg" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/rendered", + validateParams(renderedQueryValidation, "query", { allowUnknown: false }), + require("./controller/WADO-RS/rendered/instances") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/frames/{frameNumbers}/rendered: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's rendered images + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * - $ref: "#/components/parameters/frameNumbers" + * - $ref: "#/components/parameters/quality" + * - $ref: "#/components/parameters/viewport" + * - $ref: "#/components/parameters/iccprofile" + * responses: + * 200: + * $ref: "#/components/responses/RetrieveRenderedByFrameNumbers" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/frames/:frameNumber/rendered", + validateParams({ + frameNumber : intArrayJoi.intArray().items(Joi.number().integer().min(1)).single() + } , "params" , {allowUnknown : true}), + validateParams(renderedQueryValidation, "query", { allowUnknown: false }), + require("./controller/WADO-RS/rendered/instanceFrames") +); + +//#endregion + +module.exports = router; \ No newline at end of file diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index 0f782d43..f9085f21 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -12,6 +12,10 @@ InstanceModel.init({ "instancePath": { type: DataTypes.TEXT("long") }, + "x00020010": { + // Transfer Syntax UID + type: vrTypeMapping.UI + }, "x0020000D": { type: vrTypeMapping.UI, allowNull: false @@ -38,6 +42,24 @@ InstanceModel.init({ "x00200013": { type: vrTypeMapping.IS }, + "x00280008": { + // Number of Frames + type: vrTypeMapping.IS + }, + "x00281050": { + type: vrTypeMapping.DS, + get() { + const rawValue = this.getDataValue("x00281050"); + return rawValue ? rawValue.split("\\") : undefined; + } + }, + "x00281051": { + type: vrTypeMapping.DS, + get() { + const rawValue = this.getDataValue("x00281050"); + return rawValue ? rawValue.split("\\") : undefined; + } + }, "x0040A491": { type: vrTypeMapping.CS }, diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 8959abaf..8c9208b3 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -111,12 +111,17 @@ SeriesModel.getPathGroupOfInstances = async function(iParam) { x0020000D: studyUID, x0020000E: seriesUID }, - attributes: ["instancePath"] + attributes: ["instancePath", "x0020000D", "x0020000E", "x00080018"] }); let fullPathGroup = getStoreDicomFullPathGroup(instances); - return fullPathGroup; + return fullPathGroup.map(v=> { + _.set(v, "studyUID", v.x0020000D); + _.set(v, "seriesUID", v.x0020000E); + _.set(v, "instanceUID", v.x00080018); + return v; + }); } catch (e) { throw e; diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index d21adf07..9b1df50c 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -157,12 +157,17 @@ StudyModel.getPathGroupOfInstances = async function(iParam) { where: { x0020000D: studyUID }, - attributes: ["instancePath"] + attributes: ["instancePath", "x0020000D", "x0020000E", "x00080018"] }); let fullPathGroup = getStoreDicomFullPathGroup(instances); - return fullPathGroup; + return fullPathGroup.map(v=> { + _.set(v, "studyUID", v.x0020000D); + _.set(v, "seriesUID", v.x0020000E); + _.set(v, "instanceUID", v.x00080018); + return v; + }); } catch (e) { throw e; diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 490daa13..8b52e974 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -56,6 +56,7 @@ class InstancePersistentObject { this.series = series; this.instancePath = _.get(dicomJson, "instancePath", ""); + this.x00020010 = _.get(dicomJson, "00020010.Value.0", undefined); this.x0020000D = this.series.x0020000D; this.x0020000E = this.series.x0020000E; this.x00080018 = _.get(dicomJson, "00080018.Value.0", undefined); @@ -63,6 +64,9 @@ class InstancePersistentObject { this.x00080023 = _.get(dicomJson, "00080023.Value.0", undefined); this.x00080033 = _.get(dicomJson, "00080033.Value.0", undefined); this.x00200013 = _.get(dicomJson, "00200013.Value.0", undefined); + this.x00280008 = _.get(dicomJson, "00280008.Value.0", undefined); + this.x00281050 = _.get(dicomJson, "00281050.Value", undefined); + this.x00281051 = _.get(dicomJson, "00281051.Value", undefined); this.x0040A043 = _.get(dicomJson, "0040A043.Value.0", undefined); this.x0040A073 = _.get(dicomJson, "0040A073.Value.0", undefined); this.x0040A491 = _.get(dicomJson, "0040A491.Value.0", undefined); @@ -168,6 +172,7 @@ class InstancePersistentObject { let item = { json: this.json, + x00020010: this.x00020010, x0020000D: this.x0020000D, x0020000E: this.x0020000E, x00080018: this.x00080018, @@ -175,6 +180,9 @@ class InstancePersistentObject { x00080023: this.x00080023, x00080033: this.x00080033 ? Number(this.x00080033) : undefined, x00200013: this.x00200013, + x00280008: this.x00280008, + x00281050: this.x00281050 ? this.x00281050.join("\\"): undefined, + x00281051: this.x00281051 ? this.x00281051.join("\\"): undefined, x0040A073: this.x0040A073, x0040A491: this.x0040A491, x0040A493: this.x0040A493, diff --git a/routes.js b/routes.js index df0fa3d2..6aea6b4b 100644 --- a/routes.js +++ b/routes.js @@ -23,7 +23,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-metadata.route")); - app.use("/dicom-web", require("./api/dicom-web/wado-rs-rendered.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); From 1ff496d2f94679ac8acac49c8f0ebb59f0ce55c6 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Mon, 14 Aug 2023 16:41:30 +0800 Subject: [PATCH 065/365] refactor: remove duplicate code --- .../WADO-RS/service/WADO-RS.service.js | 137 ++---------------- 1 file changed, 9 insertions(+), 128 deletions(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 06d39d99..73776f0e 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -1,69 +1,16 @@ const _ = require("lodash"); -const path = require("path"); const fsP = require("fs/promises"); -const { MultipartWriter } = require("@root/utils/multipartWriter"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { raccoonConfig } = require("@root/config-class"); -const { JSONPath } = require("jsonpath-plus"); -const { DicomWebService } = require("@root/api/dicom-web/service/dicom-web.service"); -const { logger } = require("@root/utils/logs/log"); const { InstanceModel } = require("@models/sql/models/instance.model"); const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); - -/** - * - * @param {import("http").IncomingMessage} req - * @return { string } - */ -function getAcceptType(req) { - return req.headers.accept - .match(/type=(.*)/gi)[0] - .split(/[,;]/)[0] - .substring(5) - .replace(/"/g, ""); -} - -class ImageMultipartWriter { - /** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {typeof ImagePathFactory} imagePathFactory - * @param {typeof ContentTypeWriter} contentTypeWriter - */ - constructor(req, res, imagePathFactory, contentTypeWriter) { - this.request = req; - this.response = res; - this.imagePathFactory = new imagePathFactory(req.params); - this.contentTypeWriterClass = contentTypeWriter; - } - - async write() { - await this.imagePathFactory.getImagePaths(); - let checkAllImageExistResult = await this.imagePathFactory.checkAllImageExist(); - this.response.statusCode = checkAllImageExistResult.code; - if (!checkAllImageExistResult.status) { - this.response.setHeader("Content-Type", "application/dicom+json"); - return this.response.json(checkAllImageExistResult); - } - logger.info(`retrieve study's images: ${this.imagePathFactory.getPartialImagesPathString()}`); - - /** @type { ContentTypeWriter } */ - let contentTypeWriter = new this.contentTypeWriterClass( - this.imagePathFactory.imagePaths, - this.request, - this.response - ); - let writeResult = await contentTypeWriter.write(); - if (!writeResult.status) { - this.response.setHeader("Content-Type", "application/dicom+json"); - return this.response.status(writeResult.code).json(writeResult); - } - - return this.response.end(); - } -} +const { + getAcceptType, + supportInstanceMultipartType, + sendNotSupportedMediaType, + addHostnameOfBulkDataUrl, + multipartContentTypeWriter, + ImageMultipartWriter +} = require("@root/api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class ImagePathFactory { @@ -170,79 +117,13 @@ class InstanceImagePathFactory extends ImagePathFactory { async getImagePaths() { let imagePath = await InstanceModel.getPathOfInstance(this.uids); - if(imagePath) + if (imagePath) this.imagePaths = [imagePath]; else this.imagePaths = []; } } -class ContentTypeWriter { - constructor(imagePaths, req, res) { - this.imagePaths = imagePaths; - this.request = req; - this.response = res; - } - - /** - * For image pre-processing, such as converting to compressed bulk data (i.e. image/jpeg, image/dicom-rle, etc.) - */ - async preprocess() { } - - async write() { } -} - -class DicomTypeWriter extends ContentTypeWriter { - constructor(imagePaths, req, res) { - super(imagePaths, req, res); - } - - async preprocess() { } - - async write() { - let multipartWriter = new MultipartWriter(this.imagePaths, this.request, this.response); - return await multipartWriter.writeDICOMFiles("application/dicom"); - } -} - -const multipartContentTypeWriter = { - "application/dicom": DicomTypeWriter, - "application/octet-stream": DicomTypeWriter -}; - -const supportInstanceMultipartType = ["application/dicom", "application/octet-stream"]; - -/** - * - * @param { import('express').Response } res - * @param { string } type - * @returns - */ -function sendNotSupportedMediaType(res, type) { - let errorMessage = errorResponse.getNotSupportedErrorMessage(`The type ${type} is not supported, server supported \`multipart/related; type="application/dicom"\`, \`multipart/related; type="application/octet-stream"\` and \`application/zip\``); - res.writeHead(errorMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return res.end(JSON.stringify(errorMessage)); -} - -function addHostnameOfBulkDataUrl(metadata, req) { - let dicomWebService = new DicomWebService(req, undefined); - - let urItems = JSONPath({ - path: "$..BulkDataURI", - json: metadata, - resultType: "all" - }); - - for (let urItem of urItems) { - let bulkDataUriPath = JSONPath.toPathArray(urItem.path).join(".").substring(2); - let relativeUrl = _.get(metadata, bulkDataUriPath); - - _.set(metadata, bulkDataUriPath, `${dicomWebService.getBasicURL()}${relativeUrl}`); - } -} - module.exports.getAcceptType = getAcceptType; module.exports.supportInstanceMultipartType = supportInstanceMultipartType; module.exports.sendNotSupportedMediaType = sendNotSupportedMediaType; From 30e13ae38acb70c51d96391926823138a6e48cca Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Mon, 14 Aug 2023 16:47:29 +0800 Subject: [PATCH 066/365] feat: Retrieve Transaction Metadata Resources APIs --- .../metadata/retrieveInstanceMetadata.js | 65 +++++++++++++++++ .../metadata/retrieveSeriesMetadata.js | 68 ++++++++++++++++++ .../WADO-RS/metadata/retrieveStudyMetadata.js | 71 +++++++++++++++++++ api-sql/dicom-web/wado-rs-metadata.route.js | 71 +++++++++++++++++++ routes.js | 2 +- 5 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js create mode 100644 api-sql/dicom-web/wado-rs-metadata.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js new file mode 100644 index 00000000..8b47b5d1 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js @@ -0,0 +1,65 @@ +const _ = require("lodash"); +const fs = require("fs"); +const path = require("path"); +const fileExist = require("@root/utils/file/fileExist"); +const wadoService = require("../service/WADO-RS.service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { InstanceModel } = require("@models/sql/models/instance.model"); + +class RetrieveInstanceMetadataController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + + apiLogger.addTokenValue(); + apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instance Metadata] [instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); + try { + let responseMetadata = []; + + let imagePathObj = await InstanceModel.getPathOfInstance(this.request.params); + if (imagePathObj) { + let instanceDir = path.dirname(imagePathObj.instancePath); + let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); + if (await fileExist(metadataPath)) { + let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); + let metadataJson = JSON.parse(metadataJsonStr); + wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); + responseMetadata.push(metadataJson); + } + this.response.writeHead(200, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(responseMetadata)); + } + + this.response.writeHead(204); + return this.response.end(JSON.stringify( + errorResponse.getNotFoundErrorMessage( + `Not found metadata of instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}` + ) + )); + } catch(e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + console.error(errorStr); + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(); + } + } +} +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function(req, res) { + let controller = new RetrieveInstanceMetadataController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js new file mode 100644 index 00000000..ecfb2905 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js @@ -0,0 +1,68 @@ +const _ = require("lodash"); +const fs = require("fs"); +const path = require("path"); +const fileExist = require("@root/utils/file/fileExist"); +const wadoService = require("../service/WADO-RS.service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { SeriesModel } = require("@models/sql/models/series.model"); + +class RetrieveSeriesMetadataController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + + apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instances Metadata] [series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); + try { + let responseMetadata = []; + + let imagesPathList = await SeriesModel.getPathGroupOfInstances(this.request.params); + if (imagesPathList.length > 0) { + for (let imagePathObj of imagesPathList) { + let instanceDir = path.dirname(imagePathObj.instancePath); + let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); + if (await fileExist(metadataPath)) { + let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); + let metadataJson = JSON.parse(metadataJsonStr); + wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); + responseMetadata.push(metadataJson); + } + } + this.response.writeHead(200, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(responseMetadata)); + } + + this.response.writeHead(204); + return this.response.end(JSON.stringify( + errorResponse.getNotFoundErrorMessage( + `Not found metadata of series UID:${this.request.params.seriesUID} study UID: ${this.request.params.studyUID}` + ) + )); + } catch(e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + console.error(errorStr); + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(); + } + } +} +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function(req, res) { + let controller = new RetrieveSeriesMetadataController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js new file mode 100644 index 00000000..16a7bcf0 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js @@ -0,0 +1,71 @@ +const _ = require("lodash"); +const fs = require("fs"); +const path = require("path"); +const fileExist = require("@root/utils/file/fileExist"); +const wadoService = require("../service/WADO-RS.service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StudyModel } = require("@models/sql/models/study.model"); + +class RetrieveStudyMetadataController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get Study's Instances Metadata [study UID: ${this.request.params.studyUID}]`); + + try { + let responseMetadata = []; + + let pathGroupOfInstancesInStudy = await StudyModel.getPathGroupOfInstances(this.request.params); + + if (pathGroupOfInstancesInStudy.length > 0) { + + for (let imagePathObj of pathGroupOfInstancesInStudy) { + let instanceDir = path.dirname(imagePathObj.instancePath); + let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); + if (await fileExist(metadataPath)) { + let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); + let metadataJson = JSON.parse(metadataJsonStr); + wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); + responseMetadata.push(metadataJson); + } + } + + this.response.writeHead(200, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(responseMetadata)); + } + + this.response.writeHead(204); + return this.response.end(); + + } catch(e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(); + } + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function(req, res) { + let controller = new RetrieveStudyMetadataController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/wado-rs-metadata.route.js b/api-sql/dicom-web/wado-rs-metadata.route.js new file mode 100644 index 00000000..9bd25200 --- /dev/null +++ b/api-sql/dicom-web/wado-rs-metadata.route.js @@ -0,0 +1,71 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi } = require("@root/api/validator"); +const router = express(); + + +//#region WADO-RS Retrieve Transaction Metadata Resources + +/** + * @openapi + * /dicom-web/studies/{studyUID}/metadata: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's instances' metadata + * parameters: + * - $ref: "#/components/parameters/studyUID" + * responses: + * 200: + * $ref: "#/components/responses/DicomMetadata" + * + */ +router.get( + "/studies/:studyUID/metadata", + require("./controller/WADO-RS/metadata/retrieveStudyMetadata") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/metadata: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's series' instances' metadata + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * responses: + * 200: + * $ref: "#/components/responses/DicomMetadata" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/metadata", + require("./controller/WADO-RS/metadata/retrieveSeriesMetadata") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/metadata: + * get: + * tags: + * - WADO-RS + * description: Retrieve instance's metadata + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * responses: + * 200: + * $ref: "#/components/responses/DicomMetadata" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/metadata", + require("./controller/WADO-RS/metadata/retrieveInstanceMetadata") +); + +//#endregion + +module.exports = router; \ No newline at end of file diff --git a/routes.js b/routes.js index 6aea6b4b..e09c44cd 100644 --- a/routes.js +++ b/routes.js @@ -22,7 +22,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/stow-rs.route")); app.use("/dicom-web", require("./api-sql/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); - app.use("/dicom-web", require("./api/dicom-web/wado-rs-metadata.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-thumbnail.route")); From 905ff93ec63a00c3d8123fe6ade6bd579eeca67a Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Mon, 14 Aug 2023 17:29:13 +0800 Subject: [PATCH 067/365] refactor: export original function for reusing --- .../controller/WADO-RS/rendered/instanceFrames.js | 5 +---- api-sql/dicom-web/controller/WADO-RS/rendered/instances.js | 3 +-- api-sql/dicom-web/controller/WADO-RS/rendered/series.js | 3 +-- api-sql/dicom-web/controller/WADO-RS/rendered/study.js | 3 +-- .../controller/WADO-RS/service/rendered.service.js | 7 ++++++- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js b/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js index f059709b..d433035b 100644 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js @@ -1,8 +1,5 @@ const _ = require("lodash"); const renderedService = require("../service/rendered.service"); -const { - RenderedImageMultipartWriter -} = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); const { ApiLogger } = require("@root/utils/logs/api-logger"); @@ -36,7 +33,7 @@ class RetrieveRenderedInstanceFramesController extends Controller { } try { - let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( this.request, this.response, InstanceImagePathFactory, diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js b/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js index a4a8f443..0ceb5599 100644 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js @@ -1,5 +1,4 @@ const _ = require("lodash"); -const { RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); const renderedService = require("../service/rendered.service"); const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); @@ -28,7 +27,7 @@ class RetrieveRenderedInstancesController extends Controller { } try { - let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( this.request, this.response, InstanceImagePathFactory, diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/series.js b/api-sql/dicom-web/controller/WADO-RS/rendered/series.js index d037cc3e..ef2d68b6 100644 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/series.js +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/series.js @@ -1,5 +1,4 @@ const _ = require("lodash"); -const { RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); const renderedService = require("../service/rendered.service"); const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); @@ -23,7 +22,7 @@ class RetrieveRenderedSeriesController extends Controller { } try { - let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( this.request, this.response, SeriesImagePathFactory, diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/study.js b/api-sql/dicom-web/controller/WADO-RS/rendered/study.js index 630cf528..4cefe7d5 100644 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/study.js +++ b/api-sql/dicom-web/controller/WADO-RS/rendered/study.js @@ -1,5 +1,4 @@ const _ = require("lodash"); -const { RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); const renderedService = require("../service/rendered.service"); const { StudyImagePathFactory @@ -30,7 +29,7 @@ class RetrieveRenderedStudyController extends Controller { try { - let renderedImageMultipartWriter = new RenderedImageMultipartWriter( + let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( this.request, this.response, StudyImagePathFactory, diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js index 98de69e4..e8a6b329 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -1,7 +1,8 @@ const { postProcessFrameImage, writeRenderedImages, - writeSpecificFramesRenderedImages + writeSpecificFramesRenderedImages, + RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); const path = require("path"); @@ -195,7 +196,11 @@ async function getInstanceFrameObj(iParam, otherFields = {}) { } +module.exports.postProcessFrameImage = postProcessFrameImage; +module.exports.writeRenderedImages = writeRenderedImages; +module.exports.writeSpecificFramesRenderedImages = writeSpecificFramesRenderedImages; module.exports.getInstanceFrameObj = getInstanceFrameObj; +module.exports.RenderedImageMultipartWriter = RenderedImageMultipartWriter; module.exports.StudyFramesWriter = StudyFramesWriter; module.exports.SeriesFramesWriter = SeriesFramesWriter; module.exports.InstanceFramesWriter = InstanceFramesWriter; From 8fc1a0b9c1b60d88aed5a6b811d2254b690373bd Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Mon, 14 Aug 2023 17:29:55 +0800 Subject: [PATCH 068/365] feat: Retrieve Thumbnail Resources APIs --- .../WADO-RS/service/thumbnail.service.js | 155 ++++++++++++++++++ .../controller/WADO-RS/thumbnail/frame.js | 52 ++++++ .../controller/WADO-RS/thumbnail/instance.js | 51 ++++++ .../controller/WADO-RS/thumbnail/series.js | 50 ++++++ .../controller/WADO-RS/thumbnail/study.js | 49 ++++++ api-sql/dicom-web/wado-rs-thumbnail.route.js | 118 +++++++++++++ models/sql/models/instance.model.js | 29 ++++ routes.js | 2 +- 8 files changed, 505 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js create mode 100644 api-sql/dicom-web/wado-rs-thumbnail.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js new file mode 100644 index 00000000..3505388e --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -0,0 +1,155 @@ +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const renderedService = require("../service/rendered.service"); +const _ = require("lodash"); +const { + ThumbnailService, + StudyThumbnailFactory, + SeriesThumbnailFactory, + InstanceThumbnailFactory +} = require("@root/api/dicom-web/controller/WADO-RS/service/thumbnail.service"); +const { InstanceModel } = require("@models/sql/models/instance.model"); +class SqlThumbnailService extends ThumbnailService { + + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + * @param {typeof ThumbnailFactory} thumbnailFactory + */ + constructor(req, res, apiLogger, thumbnailFactory) { + super(req, res, apiLogger, thumbnailFactory); + } + + async getThumbnailAndResponse() { + if (!_.get(this.request, "query.viewport")) { + _.set(this.request, "query.viewport", "100,100"); + } + + let instanceFramesObj = await this.thumbnailFactory.getThumbnailInstance(); + if (this.checkInstanceExists(instanceFramesObj)) { + return; + } + + let thumbnail = await this.getThumbnailByInstance(instanceFramesObj); + if (thumbnail) { + return this.response.end(thumbnail, "binary"); + } + throw new Error(`Can not process this image, instanceUID: ${instanceFramesObj.instanceUID}`); + } + + async getThumbnailByInstance(instanceFramesObj) { + let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); + dicomNumberOfFrames = parseInt(dicomNumberOfFrames); + let medianFrame = 1; + if (dicomNumberOfFrames > 1) medianFrame = dicomNumberOfFrames >> 1; + if (this.request.params.frameNumber) { + medianFrame = this.request.params.frameNumber[0]; + } + + let postProcessResult = await renderedService.postProcessFrameImage(this.request, medianFrame, instanceFramesObj); + if (postProcessResult.status) { + this.response.writeHead(200, { + "Content-Type": "image/jpeg" + }); + this.apiLogger.logger.info(`Get instance's thumbnail successfully, instance UID: ${instanceFramesObj.instanceUID}`); + return postProcessResult.magick.toBuffer(); + } + return undefined; + } + + checkInstanceExists(instanceFramesObj) { + if (!instanceFramesObj) { + this.response.writeHead(404, { + "Content-Type": "application/dicom+json" + }); + let notFoundMessage = errorResponse.getNotFoundErrorMessage(`Not Found, ${this.thumbnailFactory.getUidsString()}`); + + let notFoundMessageStr = JSON.stringify(notFoundMessage); + + this.apiLogger.logger.warn(`[${notFoundMessageStr}]`); + + return this.response.end(notFoundMessageStr); + } + return undefined; + } +} + + +class SqlStudyThumbnailFactory extends StudyThumbnailFactory { + constructor(uids) { + super(uids); + } + + /** + * + * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + */ + async getThumbnailInstance() { + let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ + studyUID: this.uids.studyUID + }); + if (!medianInstance) return undefined; + + let instanceFramesObj = await renderedService.getInstanceFrameObj({ + studyUID: this.uids.studyUID, + seriesUID: medianInstance.seriesUID, + instanceUID: medianInstance.instanceUID + }); + + return instanceFramesObj; + } + +} + +class SqlSeriesThumbnailFactory extends SeriesThumbnailFactory { + constructor(uids) { + super(uids); + } + + /** + * + * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + */ + async getThumbnailInstance() { + let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ + studyUID: this.uids.studyUID, + seriesUID: this.uids.seriesUID + }); + if (!medianInstance) return undefined; + + let instanceFramesObj = await renderedService.getInstanceFrameObj({ + studyUID: this.uids.studyUID, + seriesUID: this.uids.seriesUID, + instanceUID: medianInstance.instanceUID + }); + + return instanceFramesObj; + } + +} + +class SqlInstanceThumbnailFactory extends InstanceThumbnailFactory { + constructor(uids) { + super(uids); + } + + /** + * + * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + */ + async getThumbnailInstance() { + let instanceFramesObj = await renderedService.getInstanceFrameObj({ + studyUID: this.uids.studyUID, + seriesUID: this.uids.seriesUID, + instanceUID: this.uids.instanceUID + }); + + return instanceFramesObj; + } + +} + +module.exports.ThumbnailService = SqlThumbnailService; +module.exports.StudyThumbnailFactory = SqlStudyThumbnailFactory; +module.exports.SeriesThumbnailFactory = SqlSeriesThumbnailFactory; +module.exports.InstanceThumbnailFactory = SqlInstanceThumbnailFactory; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js new file mode 100644 index 00000000..6727d720 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js @@ -0,0 +1,52 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { + ThumbnailService, + InstanceThumbnailFactory +} = require("../service/thumbnail.service"); + + + +class RetrieveFrameThumbnailController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ + series UID: ${this.request.params.seriesUID}]\ + instance UID: ${this.request.params.instanceUID}\ + frames: ${JSON.stringify(this.request.params.frameNumber)}`); + + try { + let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, InstanceThumbnailFactory); + return thumbnailService.getThumbnailAndResponse(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end({ + code: 500, + message: "An exception occurred" + }); + } + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function (req, res) { + let controller = new RetrieveFrameThumbnailController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js new file mode 100644 index 00000000..08877156 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js @@ -0,0 +1,51 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { + ThumbnailService, + InstanceThumbnailFactory +} = require("../service/thumbnail.service"); + + + +class RetrieveInstanceThumbnailController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ + series UID: ${this.request.params.seriesUID}]\ + instance UID: ${this.request.params.instanceUID}`); + + try { + let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, InstanceThumbnailFactory); + return thumbnailService.getThumbnailAndResponse(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end({ + code: 500, + message: "An exception occurred" + }); + } + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function (req, res) { + let controller = new RetrieveInstanceThumbnailController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js new file mode 100644 index 00000000..fcb00e9f --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js @@ -0,0 +1,50 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { + ThumbnailService, + SeriesThumbnailFactory +} = require("../service/thumbnail.service"); + + + +class RetrieveSeriesThumbnailController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get Study's Series' Thumbnail [study UID: ${this.request.params.studyUID},\ + series UID: ${this.request.params.seriesUID}]`); + + try { + let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, SeriesThumbnailFactory); + return thumbnailService.getThumbnailAndResponse(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end({ + code: 500, + message: "An exception occurred" + }); + } + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function (req, res) { + let controller = new RetrieveSeriesThumbnailController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js new file mode 100644 index 00000000..ad13ea8e --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js @@ -0,0 +1,49 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { + ThumbnailService, + StudyThumbnailFactory +} = require("../service/thumbnail.service"); + + + +class RetrieveStudyThumbnailController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get Study's Thumbnail [study UID: ${this.request.params.studyUID}]`); + + try { + let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, StudyThumbnailFactory); + return thumbnailService.getThumbnailAndResponse(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end({ + code: 500, + message: "An exception occurred" + }); + } + } +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function (req, res) { + let controller = new RetrieveStudyThumbnailController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/wado-rs-thumbnail.route.js b/api-sql/dicom-web/wado-rs-thumbnail.route.js new file mode 100644 index 00000000..6e982d63 --- /dev/null +++ b/api-sql/dicom-web/wado-rs-thumbnail.route.js @@ -0,0 +1,118 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi } = require("@root/api/validator"); +const router = express(); + + +//#region WADO-RS Retrieve Transaction Thumbnail Resources + +/** + * @openapi + * /dicom-web/studies/{studyUID}/thumbnail: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's instances' metadata + * parameters: + * - $ref: "#/components/parameters/studyUID" + * responses: + * 200: + * description: The response payload for WADO-RS Thumbnail + * content: + * "image/jpeg": + * schema: + * type: string + * format: binary + * + */ +router.get( + "/studies/:studyUID/thumbnail", + require("./controller/WADO-RS/thumbnail/study") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/thumbnail: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's series' thumbnail + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * responses: + * 200: + * description: The response payload for WADO-RS Thumbnail + * content: + * "image/jpeg": + * schema: + * type: string + * format: binary + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/thumbnail", + require("./controller/WADO-RS/thumbnail/series") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/thumbnail: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's Series' instances' Thumbnail + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * responses: + * 200: + * description: The response payload for WADO-RS Thumbnail + * content: + * "image/jpeg": + * schema: + * type: string + * format: binary + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/thumbnail", + require("./controller/WADO-RS/thumbnail/instance") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/frames/{frameNumbers}/thumbnail: + * get: + * tags: + * - WADO-RS + * description: Retrieve Study's instances' metadata + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * - $ref: "#/components/parameters/frameNumbers" + * responses: + * 200: + * description: The response payload for WADO-RS Thumbnail + * content: + * "image/jpeg": + * schema: + * type: string + * format: binary + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/frames/:frameNumber/thumbnail", + validateParams({ + frameNumber : intArrayJoi.intArray().items(Joi.number().integer().min(1)).single() + } , "params" , {allowUnknown : true}), + require("./controller/WADO-RS/thumbnail/frame") +); + + + +//#endregion + +module.exports = router; \ No newline at end of file diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index f9085f21..87c3493c 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -131,4 +131,33 @@ InstanceModel.getPathOfInstance = async function(iParam) { } }; +InstanceModel.getInstanceOfMedianIndex = async function (query) { + let instanceCountOfStudy = await InstanceModel.count({ + where: { + x0020000D: query.studyUID + } + }); + + let instance = await InstanceModel.findOne({ + where: { + x0020000D: query.studyUID + }, + attributes: ["x0020000D", "x0020000E", "x00080018", "instancePath"], + offset: instanceCountOfStudy >> 1, + limit: 1, + order: [ + ["x0020000D", "ASC"], + ["x0020000E", "ASC"] + ] + }); + + if (instance) { + _.set(instance, "studyUID", instance.x0020000D); + _.set(instance, "seriesUID", instance.x0020000E); + _.set(instance, "instanceUID", instance.x00080018); + } + + return instance; +}; + module.exports.InstanceModel = InstanceModel; diff --git a/routes.js b/routes.js index e09c44cd..84e61ca8 100644 --- a/routes.js +++ b/routes.js @@ -25,7 +25,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); - app.use("/dicom-web", require("./api/dicom-web/wado-rs-thumbnail.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); From b0254c5e3bc7629263f1d86c7d11c1cd3dc18051 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Mon, 14 Aug 2023 17:34:13 +0800 Subject: [PATCH 069/365] chore: add TODOs --- .../dicom-web/controller/WADO-RS/service/rendered.service.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js index e8a6b329..842e5878 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -16,6 +16,8 @@ const { raccoonConfig } = require("@root/config-class"); const { Op } = require("sequelize"); const { InstanceModel } = require("@models/sql/models/instance.model.js"); +//TODO: Add SQL version of handleImageICCProfile function + class FramesWriter { /** * @@ -195,6 +197,7 @@ async function getInstanceFrameObj(iParam, otherFields = {}) { } } +//TODO: Add SQL version of postProcessFrameImage function module.exports.postProcessFrameImage = postProcessFrameImage; module.exports.writeRenderedImages = writeRenderedImages; From 0efd9630cc299678528fb808676a3a3c697b8216 Mon Sep 17 00:00:00 2001 From: chin Date: Mon, 14 Aug 2023 22:28:14 +0800 Subject: [PATCH 070/365] feat: Retrieve Bulkdata Resources APIs --- .../controller/WADO-RS/bulkdata/bulkdata.js | 48 ++++++++++ .../controller/WADO-RS/bulkdata/instance.js | 59 ++++++++++++ .../controller/WADO-RS/bulkdata/series.js | 59 ++++++++++++ .../WADO-RS/bulkdata/service/bulkdata.js | 92 +++++++++++++++++++ .../controller/WADO-RS/bulkdata/study.js | 57 ++++++++++++ api-sql/dicom-web/wado-rs-bulkdata.route.js | 88 ++++++++++++++++++ routes.js | 2 +- 7 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js create mode 100644 api-sql/dicom-web/wado-rs-bulkdata.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js new file mode 100644 index 00000000..bba09900 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js @@ -0,0 +1,48 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { BulkDataService } = require("./service/bulkdata"); +const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); + +class BulkDataController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get bulk data ${this.request.params.binaryValuePath}\ +, from StudyInstanceUID: ${this.request.params.studyUID}\ +, SeriesInstanceUID: ${this.request.params.seriesUID}\ +, SOPInstanceUID: ${this.request.params.instanceUID}`); + + let bulkDataService = new BulkDataService(this.request, this.response); + + try { + let bulkData = await bulkDataService.getSpecificBulkData(); + await bulkDataService.writeBulkData(bulkData); + bulkDataService.multipartWriter.writeFinalBoundary(); + return this.response.end(); + } catch(e) { + apiLogger.logger.error(e); + return this.response.status(500).json( + getInternalServerErrorMessage("An exception occur") + ); + } + + } +} + + +/** + * + * @param {import("express").Request} + * @param {import("express").Response} + * @returns + */ +module.exports = async function(req, res) { + let bulkDataController = new BulkDataController(req, res); + + await bulkDataController.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js new file mode 100644 index 00000000..e54bc21f --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js @@ -0,0 +1,59 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { BulkDataService } = require("./service/bulkdata"); +const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { InstanceModel } = require("@models/sql/models/instance.model"); + +class InstanceBulkDataController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ +, SeriesInstanceUID: ${this.request.params.seriesUID}\ +, SOPInstanceUID: ${this.request.params.instanceUID}`); + + let bulkDataService = new BulkDataService(this.request, this.response); + + try { + let bulkDataArray = await bulkDataService.getInstanceBulkData(); + for (let bulkData of bulkDataArray) { + await bulkDataService.writeBulkData(bulkData); + } + + let dicomInstancePathObj = await InstanceModel.getPathOfInstance({ + studyUID: this.request.params.studyUID, + seriesUID: this.request.params.seriesUID, + instanceUID: this.request.params.instanceUID + }); + + await bulkDataService.writeBulkData(dicomInstancePathObj); + + bulkDataService.multipartWriter.writeFinalBoundary(); + return this.response.end(); + } catch(e) { + apiLogger.logger.error(e); + return this.response.status(500).json( + getInternalServerErrorMessage("An exception occur") + ); + } + + } +} + + +/** + * + * @param {import("express").Request} + * @param {import("express").Response} + * @returns + */ +module.exports = async function(req, res) { + let instanceBulkDataController = new InstanceBulkDataController(req, res); + + await instanceBulkDataController.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js new file mode 100644 index 00000000..538b80d2 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js @@ -0,0 +1,59 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { BulkDataService } = require("./service/bulkdata"); +const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { SeriesModel } = require("@models/sql/models/series.model"); + +class SeriesBulkDataController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ +, SeriesInstanceUID: ${this.request.params.seriesUID}`); + + let bulkDataService = new BulkDataService(this.request, this.response); + + try { + let bulkDataArray = await bulkDataService.getSeriesBulkData(); + for (let bulkData of bulkDataArray) { + await bulkDataService.writeBulkData(bulkData); + } + + let dicomInstancePathObjArray = await SeriesModel.getPathGroupOfInstances({ + studyUID: this.request.params.studyUID, + seriesUID: this.request.params.seriesUID + }); + + for (let instancePathObj of dicomInstancePathObjArray) { + await bulkDataService.writeBulkData(instancePathObj); + } + + bulkDataService.multipartWriter.writeFinalBoundary(); + return this.response.end(); + } catch (e) { + apiLogger.logger.error(e); + return this.response.status(500).json( + getInternalServerErrorMessage("An exception occur") + ); + } + + } +} + + +/** + * + * @param {import("express").Request} + * @param {import("express").Response} + * @returns + */ +module.exports = async function (req, res) { + let seriesBulkDataController = new SeriesBulkDataController(req, res); + + await seriesBulkDataController.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js new file mode 100644 index 00000000..9fbd16b6 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -0,0 +1,92 @@ +const { BulkDataService } = require("@root/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); +const { DicomBulkDataModel } = require("@models/sql/models/dicomBulkData.model"); +const { Op } = require("sequelize"); + +class SqlBulkDataService extends BulkDataService { + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + */ + constructor(req, res) { + super(req, res); + } + + + async getSpecificBulkData() { + + let { + studyUID, + seriesUID, + instanceUID, + binaryValuePath + } = this.request.params; + + /** @type { import("sequelize").FindOptions } */ + let findOption = { + where: { + studyUID, + seriesUID, + instanceUID, + binaryValuePath: { + [Op.like]: `%${binaryValuePath}%` + } + } + }; + + let bulkData = await DicomBulkDataModel.findOne(findOption); + + return bulkData; + } + + async getStudyBulkData() { + let { + studyUID + } = this.request.params; + + let studyBulkDataArray = await DicomBulkDataModel.findAll({ + where: { + studyUID + } + }); + + return studyBulkDataArray; + } + + async getSeriesBulkData() { + let { + studyUID, + seriesUID + } = this.request.params; + + let seriesBulkDataArray = await DicomBulkDataModel.findAll({ + where: { + studyUID, + seriesUID + } + }); + + return seriesBulkDataArray; + } + + async getInstanceBulkData() { + let { + studyUID, + seriesUID, + instanceUID + } = this.request.params; + + let instanceBulkDataArray = await DicomBulkDataModel.findAll({ + where: { + studyUID, + seriesUID, + instanceUID + } + }); + + return instanceBulkDataArray; + } +} + + +module.exports.BulkDataService = SqlBulkDataService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js new file mode 100644 index 00000000..abaf2d16 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js @@ -0,0 +1,57 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { BulkDataService } = require("./service/bulkdata"); +const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { StudyModel } = require("@models/sql/models/study.model"); + +class StudyBulkDataController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}`); + + let bulkDataService = new BulkDataService(this.request, this.response); + + try { + let bulkDataArray = await bulkDataService.getStudyBulkData(); + for (let bulkData of bulkDataArray) { + await bulkDataService.writeBulkData(bulkData); + } + + let dicomInstancePathObjArray = await StudyModel.getPathGroupOfInstances({ + studyUID: this.request.params.studyUID + }); + + for(let instancePathObj of dicomInstancePathObjArray) { + await bulkDataService.writeBulkData(instancePathObj); + } + + bulkDataService.multipartWriter.writeFinalBoundary(); + return this.response.end(); + } catch(e) { + apiLogger.logger.error(e); + return this.response.status(500).json( + getInternalServerErrorMessage("An exception occur") + ); + } + + } +} + + +/** + * + * @param {import("express").Request} + * @param {import("express").Response} + * @returns + */ +module.exports = async function(req, res) { + let studyBulkDataController = new StudyBulkDataController(req, res); + + await studyBulkDataController.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/wado-rs-bulkdata.route.js b/api-sql/dicom-web/wado-rs-bulkdata.route.js new file mode 100644 index 00000000..e3617a5f --- /dev/null +++ b/api-sql/dicom-web/wado-rs-bulkdata.route.js @@ -0,0 +1,88 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi } = require("@root/api/validator"); +const router = express(); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/bulkdata: + * get: + * tags: + * - WADO-RS + * description: Retrieve study's bulk data + * parameters: + * - $ref: "#/components/parameters/studyUID" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedOctet" + * + */ +router.get( + "/studies/:studyUID/bulkdata", + require("./controller/WADO-RS/bulkdata/study") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/bulkdata: + * get: + * tags: + * - WADO-RS + * description: Retrieve series's bulk data + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedOctet" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/bulkdata", + require("./controller/WADO-RS/bulkdata/series") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/bulkdata: + * get: + * tags: + * - WADO-RS + * description: Retrieve instance's bulk data + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedOctet" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/bulkdata", + require("./controller/WADO-RS/bulkdata/instance") +); + +/** + * @openapi + * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/bulkdata/{binaryValuePath}: + * get: + * tags: + * - WADO-RS + * description: Retrieve instance's bulk data of specific tag + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/seriesUID" + * - $ref: "#/components/parameters/instanceUID" + * - $ref: "#/components/parameters/binaryValuePath" + * responses: + * 200: + * $ref: "#/components/responses/MultipartRelatedOctet" + * + */ +router.get( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/bulkdata/:binaryValuePath", + require("./controller/WADO-RS/bulkdata/bulkdata") +); + +module.exports = router; \ No newline at end of file diff --git a/routes.js b/routes.js index 84e61ca8..7473b9d2 100644 --- a/routes.js +++ b/routes.js @@ -24,7 +24,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); - app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); From 2f0be9cde42053daebceab3a1e0c71158dfc7675 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Tue, 15 Aug 2023 12:09:06 +0800 Subject: [PATCH 071/365] feat: WADO-URI --- .../WADO-URI/controller/retrieveInstance.js | 55 +++++++++++ api-sql/WADO-URI/index.js | 45 +++++++++ api-sql/WADO-URI/service/WADO-URI.service.js | 97 +++++++++++++++++++ routes.js | 2 +- 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 api-sql/WADO-URI/controller/retrieveInstance.js create mode 100644 api-sql/WADO-URI/index.js create mode 100644 api-sql/WADO-URI/service/WADO-URI.service.js diff --git a/api-sql/WADO-URI/controller/retrieveInstance.js b/api-sql/WADO-URI/controller/retrieveInstance.js new file mode 100644 index 00000000..71c2550d --- /dev/null +++ b/api-sql/WADO-URI/controller/retrieveInstance.js @@ -0,0 +1,55 @@ +const { WadoUriService, NotFoundInstanceError } = require("../service/WADO-URI.service"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); + +class RetrieveSingleInstanceController extends Controller { + constructor(req, res) { + super(req, res); + this.service = new WadoUriService(req, res); + this.logger = new ApiLogger(this.request, "WADO-URI"); + } + + async mainProcess() { + let { + contentType + } = this.request.query; + + if (!contentType) contentType = this.request.headers.accept; + + try { + + if (contentType === "application/dicom") { + this.service.getAndResponseDicomInstance(); + } else if (contentType === "image/jpeg") { + this.service.getAndResponseJpeg(); + } else if (!contentType) { + this.service.getAndResponseDicomInstance(); + } + + } catch(e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + this.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end(JSON.stringify({ + code: 500, + message: errorStr + })); + } + + } + +} + +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ +module.exports = async function(req, res) { + let controller = new RetrieveSingleInstanceController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/WADO-URI/index.js b/api-sql/WADO-URI/index.js new file mode 100644 index 00000000..11330b89 --- /dev/null +++ b/api-sql/WADO-URI/index.js @@ -0,0 +1,45 @@ +/** + * Route + * Implement https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_9.4 + * WADO-URI + * @author Chin-Lin Lee + */ + +const { wadoUriValidationSchema } = require("@root/api/WADO-URI/middleware/validation-schema"); +const { defaultContentType } = require("@root/api/WADO-URI/middleware/default-contentType"); +const { validateByJoi} = require("@root/api/validator"); +const express = require("express"); +const router = express.Router(); + +/** + * @openapi + * /wado: + * get: + * tags: + * - WADO-URI + * description: Retrieve instance's metadata + * parameters: + * - $ref: "#/components/parameters/requestType" + * - $ref: "#/components/parameters/queryStudyUID" + * - $ref: "#/components/parameters/querySeriesUID" + * - $ref: "#/components/parameters/queryInstanceUID" + * - $ref: "#/components/parameters/contentType" + * - $ref: "#/components/parameters/frameNumber" + * - $ref: "#/components/parameters/imageQuality" + * - $ref: "#/components/parameters/region" + * - $ref: "#/components/parameters/rows" + * - $ref: "#/components/parameters/columns" + * - $ref: "#/components/parameters/windowCenter" + * - $ref: "#/components/parameters/windowWidth" + * - $ref: "#/components/parameters/iccprofile" + * responses: + * 200: + * $ref: "#/components/responses/WadoUriData" + * + */ +router.get("/", defaultContentType, validateByJoi(wadoUriValidationSchema, "query", { + allowUnknown: false +}), require("./controller/retrieveInstance")); + + +module.exports = router; \ No newline at end of file diff --git a/api-sql/WADO-URI/service/WADO-URI.service.js b/api-sql/WADO-URI/service/WADO-URI.service.js new file mode 100644 index 00000000..4e4f617f --- /dev/null +++ b/api-sql/WADO-URI/service/WADO-URI.service.js @@ -0,0 +1,97 @@ +const fs = require("fs"); +const _ = require("lodash"); +const renderedService = require("../../dicom-web/controller/WADO-RS/service/rendered.service"); +const { Dcm2JpgExecutor } = require("@models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); +const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("@models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); +const sharp = require('sharp'); +const Magick = require("@models/magick"); +const { NotFoundInstanceError, InvalidFrameNumberError, InstanceGoneError } = require("@error/dicom-instance"); +const { WadoUriService } = require("@root/api/WADO-URI/service/WADO-URI.service"); +const { InstanceModel } = require("@models/sql/models/instance.model"); +class SqlWadoUriService extends WadoUriService{ + + /** + * + * @param {import("http").IncomingMessage} req + * @param {import("http").ServerResponse} res + */ + constructor(req, res) { + super(req, res); + } + + async getDicomInstancePathObj() { + let { + studyUID, + seriesUID, + objectUID: instanceUID + } = this.request.query; + + let imagePathObj = await InstanceModel.getPathOfInstance({ + studyUID, + seriesUID, + instanceUID + }); + + if (imagePathObj) { + + try { + await fs.promises.access(imagePathObj.instancePath, fs.constants.F_OK); + } catch(e) { + console.error(e); + throw new InstanceGoneError("The image is deleted permanently, but meta data remain"); + } + + return imagePathObj; + } + + throw new NotFoundInstanceError("Not found instance"); + } + + async handleFrameNumberAndGetImageObj() { + let imagePathObj = await this.getDicomInstancePathObj(); + let instanceFramesObj = await renderedService.getInstanceFrameObj(imagePathObj); + let instanceTotalFrameNumber = _.get(instanceFramesObj, "x00280008") ? _.get(instanceFramesObj, "x00280008") : 1; + + let windowCenter = _.get(instanceFramesObj, "x00281050.0", ""); + let windowWidth = _.get(instanceFramesObj, "x00281051.0", ""); + + let transferSyntax = _.get(instanceFramesObj, "x00020010"); + let frameNumber = parseInt(_.get(this.request.query, "frameNumber", 1)); + + if (frameNumber > instanceTotalFrameNumber) { + throw new InvalidFrameNumberError(`Invalid Frame Number, total ${instanceTotalFrameNumber}, but requested ${frameNumber}`); + } + + /** @type {Dcm2JpgExecutor$Dcm2JpgOptions} */ + let options = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync(); + options.frameNumber = frameNumber; + + if (windowCenter && windowWidth) { + options.windowCenter = windowCenter; + options.windowWidth = windowWidth; + } + + let dicomFilename = instanceFramesObj.instancePath; + let jpegFile = dicomFilename.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`); + + let getFrameImageStatus = await Dcm2JpgExecutor.convertDcmToJpgFromFilename( + dicomFilename, + jpegFile, + options + ); + + if (getFrameImageStatus.status) { + + return { + imageSharp: sharp(jpegFile), + magick: new Magick(jpegFile) + }; + } + + throw new NotFoundInstanceError("Not found DICOM Instance's Jpeg, may convert error"); + } + +} + +module.exports.WadoUriService = SqlWadoUriService; +module.exports.NotFoundInstanceError = NotFoundInstanceError; \ No newline at end of file diff --git a/routes.js b/routes.js index 7473b9d2..d06ec603 100644 --- a/routes.js +++ b/routes.js @@ -29,5 +29,5 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/delete.route")); app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); - app.use("/wado", require("./api/WADO-URI")); + app.use("/wado", require("./api-sql/WADO-URI")); }; From 66ba0e2f4ebf37d0168447fc6cd95ae29381e6c7 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Tue, 15 Aug 2023 14:24:07 +0800 Subject: [PATCH 072/365] feat: delete DICOM of hierarchy levels APIs - Delete status == 1, item is in a recycle status - Delete status >= 2, item is in a waiting permanently delete status - Use schedule to delete `Delete status >= 2` and `expired for 30 days` items - Add node-schedule package - Every query add deleteStatus==0 condition > Do not query items in delete/recycle status --- .../controller/WADO-RS/deletion/instance.js | 55 +++++++++ .../controller/WADO-RS/deletion/series.js | 54 +++++++++ .../WADO-RS/deletion/service/delete.js | 78 ++++++++++++ .../controller/WADO-RS/deletion/study.js | 54 +++++++++ .../WADO-RS/service/rendered.service.js | 3 +- api-sql/dicom-web/delete.route.js | 17 +++ models/sql/deleteSchedule.js | 112 ++++++++++++++++++ models/sql/init.js | 1 + models/sql/models/instance.model.js | 45 +++++-- models/sql/models/series.model.js | 35 +++++- models/sql/models/study.model.js | 32 ++++- models/sql/po/instance.po.js | 3 +- models/sql/po/series.po.js | 3 +- models/sql/po/study.po.js | 3 +- package-lock.json | 76 ++++++++++++ package.json | 1 + routes.js | 2 +- 17 files changed, 554 insertions(+), 20 deletions(-) create mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/instance.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/series.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js create mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/study.js create mode 100644 api-sql/dicom-web/delete.route.js create mode 100644 models/sql/deleteSchedule.js diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/instance.js b/api-sql/dicom-web/controller/WADO-RS/deletion/instance.js new file mode 100644 index 00000000..f752ab9d --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/deletion/instance.js @@ -0,0 +1,55 @@ +const mongoose = require("mongoose"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { DeleteService } = require("./service/delete"); +const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { NotFoundInstanceError } = require("@error/dicom-instance"); + +class DeleteInstanceController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + let deleteService = new DeleteService(this.request, this.response, "instance"); + + try { + await deleteService.delete(); + + return this.response.status(200).json({ + Details: `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}, SOPInstanceUID: ${this.request.params.instanceUID}`, + HttpStatus: 200, + Message: "Delete Successful", + Method: "DELETE" + }); + } catch(e) { + + if (e instanceof NotFoundInstanceError) { + return this.response.status(404).json( + getNotFoundErrorMessage(e.message) + ); + } + + return this.response.status(500).json( + getInternalServerErrorMessage("An exception occur") + ); + } + + } +} + + +/** + * + * @param {import("express").Request} + * @param {import("express").Response} + * @returns + */ +module.exports = async function(req, res) { + let deleteStudyController = new DeleteInstanceController(req, res); + + await deleteStudyController.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/series.js b/api-sql/dicom-web/controller/WADO-RS/deletion/series.js new file mode 100644 index 00000000..c82f0921 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/deletion/series.js @@ -0,0 +1,54 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { DeleteService } = require("./service/delete"); +const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { NotFoundInstanceError } = require("@error/dicom-instance"); + +class DeleteSeriesController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + let deleteService = new DeleteService(this.request, this.response, "series"); + + try { + await deleteService.delete(); + + return this.response.status(200).json({ + Details: `Delete Series permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}`, + HttpStatus: 200, + Message: "Delete Successful", + Method: "DELETE" + }); + } catch(e) { + + if (e instanceof NotFoundInstanceError) { + return this.response.status(404).json( + getNotFoundErrorMessage(e.message) + ); + } + + return this.response.status(500).json( + getInternalServerErrorMessage("An exception occur") + ); + } + + } +} + + +/** + * + * @param {import("express").Request} + * @param {import("express").Response} + * @returns + */ +module.exports = async function(req, res) { + let deleteStudyController = new DeleteSeriesController(req, res); + + await deleteStudyController.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js new file mode 100644 index 00000000..a0973165 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -0,0 +1,78 @@ +const _ = require("lodash"); +const dicomSeriesModel = require("../../../../../../models/mongodb/models/dicomSeries"); +const dicomModel = require("../../../../../../models/mongodb/models/dicom"); +const fsP = require("fs/promises"); +const { NotFoundInstanceError } = require("../../../../../../error/dicom-instance"); +const { StudyModel } = require("@models/sql/models/study.model"); +const { SeriesModel } = require("@models/sql/models/series.model"); +const { InstanceModel } = require("@models/sql/models/instance.model"); + +class DeleteService { + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + * @param { "study" | "series" | "instance" } level + */ + constructor(req, res, level = "study") { + this.request = req; + this.response = res; + this.level = level; + } + + async delete() { + let deleteFns = {}; + deleteFns["study"] = async () => this.deleteStudy(); + deleteFns["series"] = async () => this.deleteSeries(); + deleteFns["instance"] = async () => this.deleteInstance(); + + await deleteFns[this.level](); + } + + async deleteStudy() { + let study = await StudyModel.findOne({ + where: { + x0020000D: this.request.params.studyUID + } + }); + + if (!study) { + throw new NotFoundInstanceError(`Can not found studyUID: ${this.request.params.studyUID} instances' files`); + } + + await study.incrementDeleteStatus(); + } + + async deleteSeries() { + let aSeries = await SeriesModel.findOne({ + where: { + x0020000D: this.request.params.studyUID, + x0020000E: this.request.params.seriesUID + } + }); + + if (!aSeries) { + throw new NotFoundInstanceError(`Can not found studyUID: ${this.request.params.studyUID}, seriesUID: ${this.request.params.seriesUID}' files`); + } + + await aSeries.incrementDeleteStatus(); + } + + async deleteInstance() { + let instance = await InstanceModel.findOne({ + where: { + x0020000D: this.request.params.studyUID, + x0020000E: this.request.params.seriesUID, + x00080018: this.request.params.instanceUID + } + }); + + if (!instance) { + throw new NotFoundInstanceError(`Can not found studyUID: ${this.request.params.studyUID}, seriesUID: ${this.request.params.seriesUID}, instanceUID: ${this.request.params.instanceUID} instances' files`); + } + + await instance.incrementDeleteStatus(); + } +} + +module.exports.DeleteService = DeleteService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/study.js b/api-sql/dicom-web/controller/WADO-RS/deletion/study.js new file mode 100644 index 00000000..6fb34ea2 --- /dev/null +++ b/api-sql/dicom-web/controller/WADO-RS/deletion/study.js @@ -0,0 +1,54 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { DeleteService } = require("./service/delete"); +const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { NotFoundInstanceError } = require("@error/dicom-instance"); + +class DeleteStudyController extends Controller { + constructor(req, res) { + super(req, res); + } + + async mainProcess() { + let apiLogger = new ApiLogger(this.request, "WADO-RS"); + apiLogger.addTokenValue(); + + let deleteService = new DeleteService(this.request, this.response, "study"); + + try { + await deleteService.delete(); + + return this.response.status(200).json({ + Details: `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}`, + HttpStatus: 200, + Message: "Delete Successful", + Method: "DELETE" + }); + } catch(e) { + + if (e instanceof NotFoundInstanceError) { + return this.response.status(404).json( + getNotFoundErrorMessage(e.message) + ); + } + + return this.response.status(500).json( + getInternalServerErrorMessage("An exception occur") + ); + } + + } +} + + +/** + * + * @param {import("express").Request} + * @param {import("express").Response} + * @returns + */ +module.exports = async function(req, res) { + let deleteStudyController = new DeleteStudyController(req, res); + + await deleteStudyController.doPipeline(); +}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js index 842e5878..3d284ffc 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -162,7 +162,8 @@ async function getInstanceFrameObj(iParam, otherFields = {}) { x00080018: instanceUID, x00080016: { [Op.notIn]: notImageSOPClass - } + }, + deleteStatus: 0 }, attributes: [ "instancePath", diff --git a/api-sql/dicom-web/delete.route.js b/api-sql/dicom-web/delete.route.js new file mode 100644 index 00000000..4eb02a06 --- /dev/null +++ b/api-sql/dicom-web/delete.route.js @@ -0,0 +1,17 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi } = require("@root/api/validator"); +const router = express(); + + +//#region Delete + +router.delete("/studies/:studyUID", require("./controller/WADO-RS/deletion/study")); + +router.delete("/studies/:studyUID/series/:seriesUID", require("./controller/WADO-RS/deletion/series")); + +router.delete("/studies/:studyUID/series/:seriesUID/instances/:instanceUID", require("./controller/WADO-RS/deletion/instance")); + +//#endregion + +module.exports = router; diff --git a/models/sql/deleteSchedule.js b/models/sql/deleteSchedule.js new file mode 100644 index 00000000..6ca46ded --- /dev/null +++ b/models/sql/deleteSchedule.js @@ -0,0 +1,112 @@ +const schedule = require("node-schedule"); +const { StudyModel } = require("./models/study.model"); +const { Op } = require("sequelize"); +const moment = require("moment"); +const { logger } = require("@root/utils/logs/log"); +const { InstanceModel } = require("./models/instance.model"); +const { SeriesModel } = require("./models/series.model"); + +// Delete dicom with delete status >= 2 +schedule.scheduleJob("*/10 * * * * *", async function () { + deleteExpireStudies().catch((e) => { + logger.error(e); + }); + deleteExpireSeries().catch((e) => { + logger.error(e); + }); + deleteExpireInstances().catch((e) => { + logger.error(e); + }); +}); + + +async function deleteExpireStudies() { + let deletedStudies = await StudyModel.findAll({ + where: { + deleteStatus: { + [Op.gte]: 2 + } + } + }); + + for (let deletedStudy of deletedStudies) { + let updateAtDate = moment(deletedStudy.getDataValue("updatedAt")); + let now = moment(); + let diff = now.diff(updateAtDate, "days"); + if (diff >= 30) { + let studyUID = deletedStudy.getDataValue("x0020000D"); + + logger.info("delete expired study: " + studyUID); + await Promise.all([ + InstanceModel.destroy({ + where: { + x0020000D: studyUID + } + }), + SeriesModel.destroy({ + where: { + x0020000D: studyUID + } + }), + deletedStudy.destroy() + ]); + + await deletedStudy.deleteStudyFolder(); + } + } +} + +async function deleteExpireSeries() { + let deletedSeries = await SeriesModel.findAll({ + where: { + deleteStatus: { + [Op.gte]: 2 + } + } + }); + + for (let aDeletedSeries of deletedSeries) { + let updateAtDate = moment(aDeletedSeries.getDataValue("updatedAt")); + let now = moment(); + let diff = now.diff(updateAtDate, "days"); + if (diff >= 30) { + let studyUID = aDeletedSeries.getDataValue("x0020000D"); + let seriesUID = aDeletedSeries.getDataValue("x0020000E"); + + logger.info("delete expired series: " + seriesUID); + await Promise.all([ + InstanceModel.destroy({ + where: { + x0020000D: studyUID, + x0020000E: seriesUID + } + }), + aDeletedSeries.destroy() + ]); + + await aDeletedSeries.deleteSeriesFolder(); + } + } +} + +async function deleteExpireInstances() { + let deletedInstances = await InstanceModel.findAll({ + where: { + deleteStatus: { + [Op.gte]: 2 + } + } + }); + + for (let deletedInstance of deletedInstances) { + let instanceUID = deletedInstance.getDataValue("x00080018"); + + let updateAtDate = moment(deletedInstance.getDataValue("updatedAt")); + let now = moment(); + let diff = now.diff(updateAtDate, "days"); + if (diff >= 30) { + logger.info("delete expired instance: " + instanceUID); + await deletedInstance.destroy(); + } + } +} \ No newline at end of file diff --git a/models/sql/init.js b/models/sql/init.js index cb9cde87..f37ea89a 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -38,6 +38,7 @@ async function initDatabasePostgres() { } async function init() { + require("./deleteSchedule"); if (raccoonConfig.sqlDbConfig.dialect === "postgres") { await initDatabasePostgres(); diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index 87c3493c..0faeaf82 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -1,3 +1,4 @@ +const fsP = require("fs/promises"); const { Sequelize, DataTypes, Model } = require("sequelize"); const _ = require("lodash"); const sequelizeInstance = require("@models/sql/instance"); @@ -5,8 +6,24 @@ const { vrTypeMapping } = require("../vrTypeMapping"); const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPath } = require("@models/mongodb/service"); +const { logger } = require("@root/utils/logs/log"); -class InstanceModel extends Model { }; +class InstanceModel extends Model { + async incrementDeleteStatus() { + let deleteStatus = this.getDataValue("deleteStatus"); + this.setDataValue("deleteStatus", deleteStatus + 1); + await this.save(); + } + + async deleteInstance() { + let instancePath = this.getDataValue("instancePath"); + logger.warn("Permanently delete instance: " + instancePath); + await fsP.rm(instancePath, { + force: true, + recursive: true + }); + } +}; InstanceModel.init({ "instancePath": { @@ -68,6 +85,10 @@ InstanceModel.init({ }, "json": { type: vrTypeMapping.JSON + }, + "deleteStatus": { + type: DataTypes.INTEGER, + defaultValue: 0 } }, { sequelize: sequelizeInstance, @@ -76,14 +97,17 @@ InstanceModel.init({ freezeTableName: true }); -InstanceModel.getDicomJson = async function(queryOptions) { +InstanceModel.getDicomJson = async function (queryOptions) { let queryBuilder = new InstanceQueryBuilder(queryOptions); let q = queryBuilder.build(); let seriesArray = await InstanceModel.findAll({ ...q, attributes: ["json", "x0020000D", "x0020000E", "x00080018"], limit: queryOptions.limit, - offset: queryOptions.skip + offset: queryOptions.skip, + where: { + deleteStatus: 0 + } }); return await Promise.all(seriesArray.map(async series => { @@ -102,7 +126,7 @@ InstanceModel.getDicomJson = async function(queryOptions) { })); }; -InstanceModel.getPathOfInstance = async function(iParam) { +InstanceModel.getPathOfInstance = async function (iParam) { let { studyUID, seriesUID, instanceUID } = iParam; try { @@ -110,14 +134,15 @@ InstanceModel.getPathOfInstance = async function(iParam) { where: { x0020000D: studyUID, x0020000E: seriesUID, - x00080018: instanceUID + x00080018: instanceUID, + deleteStatus: 0 } }); if (instance) { let instanceJson = await instance.toJSON(); - _.set(instanceJson, "instancePath", getStoreDicomFullPath(instanceJson)); + _.set(instanceJson, "instancePath", getStoreDicomFullPath(instanceJson)); _.set(instanceJson, "studyUID", instanceJson.x0020000D); _.set(instanceJson, "seriesUID", instanceJson.x0020000E); _.set(instanceJson, "instanceUID", instanceJson.x00080018); @@ -126,7 +151,7 @@ InstanceModel.getPathOfInstance = async function(iParam) { } return undefined; - } catch(e) { + } catch (e) { throw e; } }; @@ -134,13 +159,15 @@ InstanceModel.getPathOfInstance = async function(iParam) { InstanceModel.getInstanceOfMedianIndex = async function (query) { let instanceCountOfStudy = await InstanceModel.count({ where: { - x0020000D: query.studyUID + x0020000D: query.studyUID, + deleteStatus: 0 } }); let instance = await InstanceModel.findOne({ where: { - x0020000D: query.studyUID + x0020000D: query.studyUID, + deleteStatus: 0 }, attributes: ["x0020000D", "x0020000E", "x00080018", "instancePath"], offset: instanceCountOfStudy >> 1, diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 8c9208b3..eac0744b 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -1,3 +1,4 @@ +const fsP = require("fs/promises"); const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); @@ -5,8 +6,28 @@ const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO- const _ = require("lodash"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); +const { logger } = require("@root/utils/logs/log"); -class SeriesModel extends Model { }; +class SeriesModel extends Model { + getSeriesPath() { + return this.getDataValue("seriesPath"); + } + + async incrementDeleteStatus() { + let deleteStatus = this.getDataValue("deleteStatus"); + this.setDataValue("deleteStatus", deleteStatus + 1); + await this.save(); + } + + async deleteSeriesFolder() { + let seriesPath = this.getDataValue("seriesPath"); + logger.warn("Permanently delete series folder: " + seriesPath); + await fsP.rm(seriesPath, { + force: true, + recursive: true + }); + } +}; SeriesModel.init({ "seriesPath": { @@ -69,6 +90,10 @@ SeriesModel.init({ }, "json": { type: vrTypeMapping.JSON + }, + "deleteStatus": { + type: DataTypes.INTEGER, + defaultValue: 0 } }, { sequelize: sequelizeInstance, @@ -84,7 +109,10 @@ SeriesModel.getDicomJson = async function(queryOptions) { ...q, attributes: ["json", "x0020000E"], limit: queryOptions.limit, - offset: queryOptions.skip + offset: queryOptions.skip, + where: { + deleteStatus: 0 + } }); return await Promise.all(seriesArray.map(async series => { @@ -109,7 +137,8 @@ SeriesModel.getPathGroupOfInstances = async function(iParam) { let instances = await sequelizeInstance.model("Instance").findAll({ where: { x0020000D: studyUID, - x0020000E: seriesUID + x0020000E: seriesUID, + deleteStatus: 0 }, attributes: ["instancePath", "x0020000D", "x0020000E", "x00080018"] }); diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index 9b1df50c..2b465ee7 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -1,3 +1,4 @@ +const fsP = require("fs/promises"); const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); @@ -7,6 +8,7 @@ const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-R const { InstanceModel } = require("./instance.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); +const { logger } = require("@root/utils/logs/log"); class StudyModel extends Model { async getNumberOfStudyRelatedSeries() { @@ -26,6 +28,21 @@ class StudyModel extends Model { }); return count; } + + async incrementDeleteStatus() { + let deleteStatus = this.getDataValue("deleteStatus"); + this.setDataValue("deleteStatus", deleteStatus + 1); + await this.save(); + } + + async deleteStudyFolder() { + let studyPath = this.getDataValue("studyPath"); + logger.warn("Permanently delete study folder: " + studyPath); + await fsP.rm(studyPath, { + force: true, + recursive: true + }); + } }; StudyModel.init({ @@ -74,6 +91,10 @@ StudyModel.init({ }, "json": { type: vrTypeMapping.JSON + }, + "deleteStatus": { + type: DataTypes.INTEGER, + defaultValue: 0 } }, { sequelize: sequelizeInstance, @@ -85,7 +106,8 @@ StudyModel.init({ StudyModel.updateModalitiesInStudy = async function (study) { let seriesArray = await SeriesModel.findAll({ where: { - x0020000D: study.x0020000D + x0020000D: study.x0020000D, + deleteStatus: 0 }, attributes: [ [Sequelize.fn("DISTINCT", Sequelize.col("x00080060")), "modality"] @@ -115,7 +137,10 @@ StudyModel.getDicomJson = async function (queryOptions) { ...q, attributes: ["json"], limit: queryOptions.limit, - offset: queryOptions.skip + offset: queryOptions.skip, + where: { + deleteStatus: 0 + } }); @@ -155,7 +180,8 @@ StudyModel.getPathGroupOfInstances = async function(iParam) { try { let instances = await sequelizeInstance.model("Instance").findAll({ where: { - x0020000D: studyUID + x0020000D: studyUID, + deleteStatus: 0 }, attributes: ["instancePath", "x0020000D", "x0020000E", "x00080018"] }); diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 8b52e974..59316694 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -187,7 +187,8 @@ class InstancePersistentObject { x0040A491: this.x0040A491, x0040A493: this.x0040A493, x0040A730: this.x0040A730, - instancePath: this.instancePath + instancePath: this.instancePath, + deleteStatus: 0 }; let [instance, created] = await InstanceModel.findOrCreate({ diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index 105c7a37..5225f7c6 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -153,7 +153,8 @@ class SeriesPersistentObject { x00400244: this.x00400244, x00400245: this.x00400245 ? Number(this.x00400245) : undefined, x00080031: this.x00080031 ? Number(this.x00080031) : undefined, - seriesPath: this.seriesPath + seriesPath: this.seriesPath, + deleteStatus: 0 }; let [series, created] = await SeriesModel.findOrCreate({ diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js index 81b87566..c3b14c99 100644 --- a/models/sql/po/study.po.js +++ b/models/sql/po/study.po.js @@ -69,7 +69,8 @@ class StudyPersistentObject { x00200010 : this.x00200010, x00201206 : this.x00201206, x00201208 : this.x00201208, - studyPath: this.studyPath + studyPath: this.studyPath, + deleteStatus: 0 }; let [study, created] = await StudyModel.findOrCreate({ diff --git a/package-lock.json b/package-lock.json index bc7a1725..333c02e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "moment": "^2.29.3", "moment-timezone": "^0.5.34", "mongoose": "^6.7.2", + "node-schedule": "^2.1.1", "passport": "^0.6.0", "passport-local": "^1.0.0", "path-match": "^1.2.4", @@ -2899,6 +2900,17 @@ "node": ">= 10" } }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5220,6 +5232,11 @@ "node": ">=8.0" } }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "node_modules/loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -5240,6 +5257,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.0.tgz", + "integrity": "sha512-7eDo4Pt7aGhoCheGFIuq4Xa2fJm4ZpmldpGhjTYBNUYNCN6TIEP6v7chwwwt3KRp7YR+rghbfvjyo3V5y9hgBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6205,6 +6230,19 @@ } } }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -7805,6 +7843,11 @@ "npm": ">= 3.0.0" } }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11270,6 +11313,14 @@ "readable-stream": "^3.4.0" } }, + "cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "requires": { + "luxon": "^3.2.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -13043,6 +13094,11 @@ "streamroller": "^3.1.5" } }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -13060,6 +13116,11 @@ "yallist": "^4.0.0" } }, + "luxon": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.0.tgz", + "integrity": "sha512-7eDo4Pt7aGhoCheGFIuq4Xa2fJm4ZpmldpGhjTYBNUYNCN6TIEP6v7chwwwt3KRp7YR+rghbfvjyo3V5y9hgBw==" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -13764,6 +13825,16 @@ "whatwg-url": "^5.0.0" } }, + "node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "requires": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + } + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -14952,6 +15023,11 @@ "smart-buffer": "^4.2.0" } }, + "sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index c899dc01..8727272b 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "moment": "^2.29.3", "moment-timezone": "^0.5.34", "mongoose": "^6.7.2", + "node-schedule": "^2.1.1", "passport": "^0.6.0", "passport-local": "^1.0.0", "path-match": "^1.2.4", diff --git a/routes.js b/routes.js index d06ec603..c8e9a37e 100644 --- a/routes.js +++ b/routes.js @@ -26,7 +26,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); - app.use("/dicom-web", require("./api/dicom-web/delete.route")); + app.use("/dicom-web", require("./api-sql/dicom-web/delete.route")); app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); app.use("/wado", require("./api-sql/WADO-URI")); From dd48303a674286508ea8a34e6a142f4ff78cb765 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 15 Aug 2023 22:34:25 +0800 Subject: [PATCH 073/365] fix: incorrect deletion path of study and series # Problems - The path is relative # Solutions - Use path.join to create absolute path --- models/sql/models/series.model.js | 4 +++- models/sql/models/study.model.js | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index eac0744b..d44e1fb8 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -1,4 +1,5 @@ const fsP = require("fs/promises"); +const path = require("path"); const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); @@ -7,6 +8,7 @@ const _ = require("lodash"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); +const { raccoonConfig } = require("@root/config-class"); class SeriesModel extends Model { getSeriesPath() { @@ -22,7 +24,7 @@ class SeriesModel extends Model { async deleteSeriesFolder() { let seriesPath = this.getDataValue("seriesPath"); logger.warn("Permanently delete series folder: " + seriesPath); - await fsP.rm(seriesPath, { + await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, seriesPath), { force: true, recursive: true }); diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index 2b465ee7..2b77f3e5 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -1,4 +1,5 @@ const fsP = require("fs/promises"); +const path = require("path"); const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); @@ -9,8 +10,9 @@ const { InstanceModel } = require("./instance.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); +const { raccoonConfig } = require("@root/config-class"); -class StudyModel extends Model { +class StudyModel extends Model { async getNumberOfStudyRelatedSeries() { let count = await SeriesModel.count({ where: { @@ -38,7 +40,7 @@ class StudyModel extends Model { async deleteStudyFolder() { let studyPath = this.getDataValue("studyPath"); logger.warn("Permanently delete study folder: " + studyPath); - await fsP.rm(studyPath, { + await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, studyPath), { force: true, recursive: true }); @@ -174,7 +176,7 @@ StudyModel.getDicomJson = async function (queryOptions) { })); }; -StudyModel.getPathGroupOfInstances = async function(iParam) { +StudyModel.getPathGroupOfInstances = async function (iParam) { let { studyUID } = iParam; try { @@ -185,10 +187,10 @@ StudyModel.getPathGroupOfInstances = async function(iParam) { }, attributes: ["instancePath", "x0020000D", "x0020000E", "x00080018"] }); - + let fullPathGroup = getStoreDicomFullPathGroup(instances); - return fullPathGroup.map(v=> { + return fullPathGroup.map(v => { _.set(v, "studyUID", v.x0020000D); _.set(v, "seriesUID", v.x0020000E); _.set(v, "instanceUID", v.x00080018); From 55ef94fc6afecf6273319d38f4cb8bf5c3a223df Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 15 Aug 2023 22:37:17 +0800 Subject: [PATCH 074/365] fix: missing delete instance file in deletion --- models/sql/deleteSchedule.js | 1 + models/sql/models/instance.model.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/models/sql/deleteSchedule.js b/models/sql/deleteSchedule.js index 6ca46ded..d24bb98f 100644 --- a/models/sql/deleteSchedule.js +++ b/models/sql/deleteSchedule.js @@ -107,6 +107,7 @@ async function deleteExpireInstances() { if (diff >= 30) { logger.info("delete expired instance: " + instanceUID); await deletedInstance.destroy(); + await deletedInstance.deleteInstance(); } } } \ No newline at end of file diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index 0faeaf82..2a265ac9 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -1,4 +1,5 @@ const fsP = require("fs/promises"); +const path = require("path"); const { Sequelize, DataTypes, Model } = require("sequelize"); const _ = require("lodash"); const sequelizeInstance = require("@models/sql/instance"); @@ -7,6 +8,7 @@ const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QID const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPath } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); +const { raccoonConfig } = require("@root/config-class"); class InstanceModel extends Model { async incrementDeleteStatus() { @@ -18,7 +20,7 @@ class InstanceModel extends Model { async deleteInstance() { let instancePath = this.getDataValue("instancePath"); logger.warn("Permanently delete instance: " + instancePath); - await fsP.rm(instancePath, { + await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, instancePath), { force: true, recursive: true }); From 41092635f6fbcd95263b6e6349fe3838c2eed6b1 Mon Sep 17 00:00:00 2001 From: chin Date: Tue, 15 Aug 2023 22:41:11 +0800 Subject: [PATCH 075/365] chore: remove completed TODO --- .../controller/STOW-RS/service/dicom-jpeg-generator.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js index c4a2a0bd..7515883c 100644 --- a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js @@ -40,7 +40,6 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { } /** - * TODO * @private */ async insertEndTask_() { @@ -57,7 +56,6 @@ class SqlDicomJpegGenerator extends DicomJpegGenerator { } /** - * TODO * @private * @param {string} message */ From 588f05fc9c6d1c539b6707e3a328ef9a217bf973 Mon Sep 17 00:00:00 2001 From: chin Date: Wed, 16 Aug 2023 12:55:45 +0800 Subject: [PATCH 076/365] feat: support iccprofile --- .../WADO-RS/service/rendered.service.js | 115 +++++++++++++++++- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js index 3d284ffc..cdaab2ed 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -1,22 +1,69 @@ const { - postProcessFrameImage, + handleImageQuality, + handleViewport, writeRenderedImages, writeSpecificFramesRenderedImages, RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); - +const fs = require("fs"); +const sharp = require("sharp"); const path = require("path"); const _ = require("lodash"); const { MultipartWriter } = require("@root/utils/multipartWriter"); const notImageSOPClass = require("@models/DICOM/dicomWEB/notImageSOPClass"); +const Magick = require("@models/magick"); + const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); const { logger } = require("@root/utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); const { Op } = require("sequelize"); const { InstanceModel } = require("@models/sql/models/instance.model.js"); +const { DicomBulkDataModel } = require("@models/sql/models/dicomBulkData.model"); +const { default: Dcm2JpgExecutor } = require("@java-wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); +const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("@java-wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); -//TODO: Add SQL version of handleImageICCProfile function +/** + * + * @param {*} param The req.query + * @param {Magick} magick + * @param {string} instanceID + */ +async function handleImageICCProfile(param, magick, instanceID) { + let iccProfileAction = { + "no" : async ()=> {}, + "yes": async ()=> { + let iccProfileBinaryFile = await DicomBulkDataModel.findOne({ + where: { + binaryValuePath: "00480105.Value.0.00282000.InlineBinary", + instanceUID: instanceID + } + }); + if(!iccProfileBinaryFile) throw new Error("The Image dose not have icc profile tag"); + let iccProfileSrc = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename); + let dest = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename + `.icc`); + if (!fs.existsSync(dest)) fs.copyFileSync(iccProfileSrc, dest); + magick.iccProfile(dest); + }, + "srgb": async ()=> { + magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/sRGB.icc")); + }, + "adobergb": async () => { + magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/adobeRGB.icc")); + }, + "rommrgb": async ()=> { + magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/rommRGB.icc")); + } + }; + try { + if (param.iccprofile) { + await iccProfileAction[param.iccprofile](); + } + } catch(e) { + console.error("set icc profile error:" , e); + throw e; + } +} class FramesWriter { /** @@ -198,7 +245,67 @@ async function getInstanceFrameObj(iParam, otherFields = {}) { } } -//TODO: Add SQL version of postProcessFrameImage function +/** + * + * @param {import("http").IncomingMessage} req + * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").InstanceFrameObj} instanceFramesObj + * @returns + */ +async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { + try { + + let dicomFilename = instanceFramesObj.instancePath; + let jpegFile = dicomFilename.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`); + + + + let dcm2jpgOptions = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync(); + dcm2jpgOptions.frameNumber = frameNumber; + let getFrameImageStatus = await Dcm2JpgExecutor.convertDcmToJpgFromFilename( + dicomFilename, + jpegFile, + dcm2jpgOptions + ); + + if (getFrameImageStatus.status) { + let imageSharp = sharp(jpegFile); + let magick = new Magick(jpegFile); + handleImageQuality( + req.query, + magick + ); + await handleImageICCProfile( + req.query, + magick, + instanceFramesObj.instanceUID + ); + await handleViewport( + req.query, + imageSharp, + magick + ); + await magick.execCommand(); + return { + status: true, + message: "process successful", + magick: magick + }; + } + return { + status: false, + message: "get frame image failed", + magick: undefined + }; + } catch(e) { + console.error(e); + return { + status: false, + message: e, + magick: undefined + }; + } +} + module.exports.postProcessFrameImage = postProcessFrameImage; module.exports.writeRenderedImages = writeRenderedImages; From 90dc0d9efb6a1be4917050a8067202ddaf5572bc Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 23 Aug 2023 15:34:06 +0800 Subject: [PATCH 077/365] chore: delete expired instance every hour --- models/sql/deleteSchedule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/sql/deleteSchedule.js b/models/sql/deleteSchedule.js index d24bb98f..81cadb73 100644 --- a/models/sql/deleteSchedule.js +++ b/models/sql/deleteSchedule.js @@ -7,7 +7,7 @@ const { InstanceModel } = require("./models/instance.model"); const { SeriesModel } = require("./models/series.model"); // Delete dicom with delete status >= 2 -schedule.scheduleJob("*/10 * * * * *", async function () { +schedule.scheduleJob("0 0 */1 * * *", async function () { deleteExpireStudies().catch((e) => { logger.error(e); }); From adf0455c4cd87443e8dac1da57855aaab3580be3 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 23 Aug 2023 17:51:26 +0800 Subject: [PATCH 078/365] feat: series query with series instance uid --- .../dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index 6f5e8569..dc4277d5 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -103,6 +103,9 @@ class SeriesQueryBuilder extends BaseQueryBuilder { return this.getOrQuery(dictionary.keyword.SeriesNumber, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } + getSeriesInstanceUID(values) { + return this.getOrQuery(dictionary.keyword.SeriesInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); + } } From 24f4fe56d1d9cbd30b13e86192237917050a4b24 Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Wed, 23 Aug 2023 17:52:06 +0800 Subject: [PATCH 079/365] chore: initial DIMSE sql version --- .../QIDO-RS/service/QIDO-RS.service.js | 3 +- dimse-sql/c-find.js | 67 +++ dimse-sql/c-get.js | 114 ++++ dimse-sql/c-move.js | 165 ++++++ dimse-sql/c-store.js | 49 ++ dimse-sql/index.js | 280 +++++++++ dimse-sql/instanceQueryTask.js | 148 +++++ dimse-sql/level.js | 24 + dimse-sql/patientQueryTask.js | 172 ++++++ dimse-sql/queryBuilder.js | 26 + dimse-sql/queryTagsOfEachLevel.js | 33 ++ dimse-sql/seriesQueryTask.js | 144 +++++ dimse-sql/stgcmt.js | 149 +++++ dimse-sql/studyQueryTask.js | 141 +++++ dimse-sql/utils.js | 100 ++++ models/sql/models/instance.model.js | 13 + models/sql/models/patient.model.js | 16 +- models/sql/models/series.model.js | 13 + models/sql/models/study.model.js | 13 + package-lock.json | 532 ++++++++++++------ server.js | 2 +- 21 files changed, 2032 insertions(+), 172 deletions(-) create mode 100644 dimse-sql/c-find.js create mode 100644 dimse-sql/c-get.js create mode 100644 dimse-sql/c-move.js create mode 100644 dimse-sql/c-store.js create mode 100644 dimse-sql/index.js create mode 100644 dimse-sql/instanceQueryTask.js create mode 100644 dimse-sql/level.js create mode 100644 dimse-sql/patientQueryTask.js create mode 100644 dimse-sql/queryBuilder.js create mode 100644 dimse-sql/queryTagsOfEachLevel.js create mode 100644 dimse-sql/seriesQueryTask.js create mode 100644 dimse-sql/stgcmt.js create mode 100644 dimse-sql/studyQueryTask.js create mode 100644 dimse-sql/utils.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index fdedc2eb..0dfb8dfb 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -139,4 +139,5 @@ function convertAllQueryToDicomTag(iParam) { return newQS; } -module.exports.SqlQidoRsService = SqlQidoRsService; \ No newline at end of file +module.exports.SqlQidoRsService = SqlQidoRsService; +module.exports.convertAllQueryToDicomTag = convertAllQueryToDicomTag; \ No newline at end of file diff --git a/dimse-sql/c-find.js b/dimse-sql/c-find.js new file mode 100644 index 00000000..51735bbe --- /dev/null +++ b/dimse-sql/c-find.js @@ -0,0 +1,67 @@ +const { default: Attributes } = require("@dcm4che/data/Attributes"); +const { default: UID } = require("@dcm4che/data/UID"); +const { default: Association } = require("@dcm4che/net/Association"); +const { default: Dimse } = require("@dcm4che/net/Dimse"); +const { default: Status } = require("@dcm4che/net/Status"); +const { default: PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); +const { default: QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); +const { default: EnumSet } = require("@java-wrapper/java/util/EnumSet"); +const { default: BasicModCFindSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP"); +const { createCFindSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject"); +const { JsPatientQueryTask } = require("./patientQueryTask"); +const { JsStudyQueryTask } = require("./studyQueryTask"); +const { JsSeriesQueryTask } = require("./seriesQueryTask"); +const { JsInstanceQueryTask } = require("./instanceQueryTask"); +const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); +const { JsCFindScp } = require("@root/dimse/c-find"); + +class SqlJsCFindScp extends JsCFindScp{ + constructor() { + super(); + } + + getCFindScpInjectProxyMethods() { + /** + * @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject").CFindSCPInjectInterface } + */ + const cFindScpInjectProxyMethods = { + /** + * + * @param {Association} as + * @param {PresentationContext} pc + * @param {Dimse} dimse + * @param {Attributes} rq + * @param {Attributes} keys + */ + onDimseRQ: async (as, pc, dimse, rq, keys) => { + if (await dimse.compareTo(Dimse.C_FIND_RQ) !== 0) { + throw new Error(Status.UnrecognizedOperation); + } + let queryTask = await cFindScpInjectProxyMethods.calculateMatches(as, pc, rq, keys); + let applicationEntity = await as.getApplicationEntity(); + let device = await applicationEntity.getDevice(); + await device.execute(queryTask); + }, + calculateMatches: async (as, pc, rq, keys) => { + try { + let level = await this.scpObj.getQrLevel(as, pc, rq, keys); + if (await level.compareTo(QueryRetrieveLevel2.PATIENT) === 0) { + return await (new JsPatientQueryTask(as, pc, rq, keys)).get(); + } else if (await level.compareTo(QueryRetrieveLevel2.STUDY) === 0) { + return await (new JsStudyQueryTask(as, pc, rq, keys)).get(); + } else if (await level.compareTo(QueryRetrieveLevel2.SERIES) === 0) { + return await (new JsSeriesQueryTask(as, pc, rq, keys)).get(); + } else if (await level.compareTo(QueryRetrieveLevel2.IMAGE) === 0) { + return await (new JsInstanceQueryTask(as, pc, rq, keys)).get(); + } + } catch (e) { + console.error(e); + } + } + }; + + return cFindScpInjectProxyMethods; + } +} + +module.exports.SqlJsCFindScp = SqlJsCFindScp; \ No newline at end of file diff --git a/dimse-sql/c-get.js b/dimse-sql/c-get.js new file mode 100644 index 00000000..625c3ac3 --- /dev/null +++ b/dimse-sql/c-get.js @@ -0,0 +1,114 @@ +const { UID } = require("@dcm4che/data/UID"); +const { createCGetSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CGetSCPInject"); +const { default: SimpleCGetSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCGetSCP"); +const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); +const { getInstancesFromKeysAttr } = require("./utils"); +const { default: RetrieveTaskImpl } = require("@dcm4che/tool/dcmqrscp/RetrieveTaskImpl"); +const { Dimse } = require("@dcm4che/net/Dimse"); +const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); +const { default: QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); + +class JsCGetScp { + constructor() { } + + getPatientRootLevel() { + const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + let simpleCGetScp = new SimpleCGetSCP( + cGetScpInject, + UID.PatientRootQueryRetrieveInformationModelGet, + PATIENT_ROOT_LEVELS + ); + + this.scpObj = simpleCGetScp; + + return simpleCGetScp; + } + + getStudyRootLevel() { + const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + let simpleCGetScp = new SimpleCGetSCP( + cGetScpInject, + UID.StudyRootQueryRetrieveInformationModelGet, + STUDY_ROOT_LEVELS + ); + + this.scpObj = simpleCGetScp; + + return simpleCGetScp; + } + + getPatientStudyOnlyLevel() { + const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + let simpleCGetScp = new SimpleCGetSCP( + cGetScpInject, + UID.PatientStudyOnlyQueryRetrieveInformationModelGet, + PATIENT_STUDY_ONLY_LEVELS + ); + + this.scpObj = simpleCGetScp; + + return simpleCGetScp; + } + + getCompositeLevel() { + const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + let simpleCGetScp = new SimpleCGetSCP( + cGetScpInject, + UID.CompositeInstanceRetrieveWithoutBulkDataGet, + EnumSet.ofSync(QueryRetrieveLevel2.IMAGE) + ); + + this.scpObj = simpleCGetScp; + + return simpleCGetScp; + } + + getCGetScpInjectProxyMethods() { + const cGetScpInjectProxyMethods = { + /** + * + * @param {Association} as + * @param {PresentationContext} pc + * @param {Attributes} rq + * @param {Attributes} keys + */ + calculateMatches: async (as, pc, rq, keys) => { + let instances = await getInstancesFromKeysAttr(keys); + if (await instances.isEmpty()) { + return null; + } + + let withoutBulkData = await (await this.scpObj.getQrLevels()).size() == 1; + let retrieveTask = await RetrieveTaskImpl.newInstanceAsync( + Dimse.C_GET_RQ, + as, + pc, + rq, + instances, + as, + withoutBulkData, + 0 + ); + await retrieveTask.setSendPendingRSP(false); + + return retrieveTask; + } + }; + + return cGetScpInjectProxyMethods; + }; +} + +module.exports.JsCGetScp = JsCGetScp; \ No newline at end of file diff --git a/dimse-sql/c-move.js b/dimse-sql/c-move.js new file mode 100644 index 00000000..f3958ed8 --- /dev/null +++ b/dimse-sql/c-move.js @@ -0,0 +1,165 @@ +const _ = require("lodash"); +const path = require("path"); +const { mongoose } = require("mongoose"); + +const { Attributes } = require("@dcm4che/data/Attributes"); +const { Tag } = require("@dcm4che/data/Tag"); +const { Association } = require("@dcm4che/net/Association"); +const { Status } = require("@dcm4che/net/Status"); +const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); +const { DicomServiceError } = require("@error/dicom-service"); +const { createCMoveSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CMoveSCPInject"); +const { DimseQueryBuilder } = require("./queryBuilder"); +const { File } = require("@java-wrapper/java/io/File"); +const { raccoonConfig } = require("@root/config-class"); +const { SimpleCMoveSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCMoveSCP"); +const { UID } = require("@dcm4che/data/UID"); + +const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); +const { importClass } = require("java-bridge"); +const { default: InstanceLocator } = require("@dcm4che/net/service/InstanceLocator"); +const { default: AAssociateRQ } = require("@dcm4che/net/pdu/AAssociateRQ"); +const { default: Connection } = require("@dcm4che/net/Connection"); +const { default: RetrieveTaskImpl } = require("@dcm4che/tool/dcmqrscp/RetrieveTaskImpl"); +const { Dimse } = require("@dcm4che/net/Dimse"); +const { getInstancesFromKeysAttr } = require("./utils"); + +class JsCMoveScp { + constructor(dcmQrScp) { + /** @type { import("./index").DcmQrScp } */ + this.dcmQrScp = dcmQrScp; + } + + getPatientRootLevel() { + const cMoveScpInject = createCMoveSCPInjectProxy(this.getCMoveScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + return new SimpleCMoveSCP( + cMoveScpInject, + UID.PatientRootQueryRetrieveInformationModelMove, + PATIENT_ROOT_LEVELS + ); + } + + getStudyRootLevel() { + const cMoveScpInject = createCMoveSCPInjectProxy(this.getCMoveScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + return new SimpleCMoveSCP( + cMoveScpInject, + UID.StudyRootQueryRetrieveInformationModelMove, + STUDY_ROOT_LEVELS + ); + } + + getPatientStudyOnlyLevel() { + const cMoveScpInject = createCMoveSCPInjectProxy(this.getCMoveScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + return new SimpleCMoveSCP( + cMoveScpInject, + UID.PatientStudyOnlyQueryRetrieveInformationModelMove, + PATIENT_STUDY_ONLY_LEVELS + ); + } + + getCMoveScpInjectProxyMethods() { + /** @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/CMoveSCPInject").CMoveSCPInjectInterface } */ + const cMoveScpInjectProxyMethods = { + /** + * + * @param {Association} as + * @param {PresentationContext} pc + * @param {Attributes} rq + * @param {Attributes} keys + */ + calculateMatches: async (as, pc, rq, keys) => { + try { + let moveDest = await rq.getString(Tag.MoveDestination); + const remote = this.dcmQrScp.getRemoteConnection(moveDest); + if (!remote) { + throw new DicomServiceError(Status.MoveDestinationUnknown, `Move Destination: ${moveDest} unknown`); + } + + let instances = await getInstancesFromKeysAttr(keys); + if (await instances.isEmpty()) { + return null; + } + + let aAssociateRq = await this.makeAAssociateRQ_(await as.getLocalAET(), moveDest, instances); + let storeAssociation = await this.openStoreAssociation_(as, remote, aAssociateRq); + let retrieveTask = await RetrieveTaskImpl.newInstanceAsync( + Dimse.C_MOVE_RQ, + as, + pc, + rq, + instances, + storeAssociation, + false, + 0 + ); + await retrieveTask.setSendPendingRSPInterval(0); + return retrieveTask; + } catch (e) { + console.error(e); + throw e; + } + } + }; + + return cMoveScpInjectProxyMethods; + } + + /** + * @private + * @param {string} callingAet + * @param {string} calledAet + * @param {*} matches + */ + async makeAAssociateRQ_(callingAet, calledAet, matches) { + let aAssociateRq = await AAssociateRQ.newInstanceAsync(); + await aAssociateRq.setCallingAET(callingAet); + await aAssociateRq.setCalledAET(calledAet); + let matchesArray = await matches.toArray(); + for (let match of matchesArray) { + if (await aAssociateRq.addPresentationContextFor(match.cuid, match.tsuid)) { + + if (!UID.ExplicitVRLittleEndian === match.tsuid) { + await aAssociateRq.addPresentationContextFor(match.cuid, UID.ExplicitVRLittleEndian); + } + + if (!UID.ImplicitVRLittleEndian === match.tsuid) { + await aAssociateRq.addPresentationContextFor(match.cuid, UID.ImplicitVRLittleEndian); + } + } + } + return aAssociateRq; + } + + /** + * @private + * @param {Association} as + * @param {Connection} remote + * @param {AAssociateRQ} aAssociateRq + * @returns + */ + async openStoreAssociation_(as, remote, aAssociateRq) { + try { + let applicationEntity = await as.getApplicationEntity(); + + return await applicationEntity.connect( + await as.getConnection(), + remote, + aAssociateRq + ); + + } catch (e) { + throw new DicomServiceError(Status.UnableToPerformSubOperations, e); + } + } +} + +module.exports.JsCMoveScp = JsCMoveScp; \ No newline at end of file diff --git a/dimse-sql/c-store.js b/dimse-sql/c-store.js new file mode 100644 index 00000000..faea344e --- /dev/null +++ b/dimse-sql/c-store.js @@ -0,0 +1,49 @@ +const path = require("path"); +const { createCStoreSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CStoreSCPInject"); +const { default: SimpleCStoreSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCStoreSCP"); +const { default: File } = require("@java-wrapper/java/io/File"); +const { SqlStowRsService: StowRsService } = require("@root/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service"); + +const cStoreScpInjectProxy = createCStoreSCPInjectProxy({ + postDimseRQ: async (association, presentationContext, dimse, requestAttr, data, responseAttr) => { + await association.tryWriteDimseRSP(presentationContext, responseAttr); + }, + postStore: async (association, presentationContext, requestAttr, data, responseAttr, file) => { + let absPath = await file.getAbsolutePath(); + + let stowRsService = new StowRsService({ + headers: { + host: "fake-host" + }, + params: {} + }, []); + + /** @type {formidable.File} */ + let fileObj = { + filepath: path.resolve(absPath), + originalFilename: path.basename(absPath) + }; + + try { + await stowRsService.storeInstance(fileObj); + } catch (e) { + throw e; + } + } +}, { + keepAsDaemon: true +}); + +class JsCStoreScp { + constructor() {} + + get() { + let storageDir = new File( + path.join(__dirname, "../tempUploadFiles") + ); + let basicCStoreScp = new SimpleCStoreSCP(cStoreScpInjectProxy, storageDir, ["*"]); + return basicCStoreScp; + } +} + +module.exports.JsCStoreScp = JsCStoreScp; \ No newline at end of file diff --git a/dimse-sql/index.js b/dimse-sql/index.js new file mode 100644 index 00000000..af813de9 --- /dev/null +++ b/dimse-sql/index.js @@ -0,0 +1,280 @@ +const _ = require("lodash"); +const { java } = require("@models/DICOM/dcm4che/java-instance"); +const { importClass, appendClasspath, stdout, newProxy } = require("java-bridge"); +const glob = require("glob"); +const path = require("path"); + +const { ApplicationEntity } = require("@dcm4che/net/ApplicationEntity"); +const { BasicCEchoSCP } = require("@dcm4che/net/service/BasicCEchoSCP"); +const { Connection } = require("@dcm4che/net/Connection"); +const { Device } = require("@dcm4che/net/Device"); +const { DicomServiceRegistry } = require("@dcm4che/net/service/DicomServiceRegistry"); +const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); +const { QueryOption } = require("@dcm4che/net/QueryOption"); +const { TransferCapability } = require("@dcm4che/net/TransferCapability"); +const { TransferCapability$Role: TransferCapabilityRole } = require("@dcm4che/net/TransferCapability$Role"); +const { JsCStoreScp } = require("./c-store"); +const { SqlJsCFindScp: JsCFindScp } = require("./c-find"); +const { default: CLIUtils } = require("@dcm4che/tool/common/CLIUtils"); +const { JsCMoveScp } = require("./c-move"); +const fileExist = require("@root/utils/file/fileExist"); +const { JsCGetScp } = require("./c-get"); +const { JsStgCmtScp } = require("./stgcmt"); +const { raccoonConfig } = require("@root/config-class"); +const { Connection$EndpointIdentificationAlgorithm } = require("@dcm4che/net/Connection$EndpointIdentificationAlgorithm"); +const { default: SSLManagerFactory } = require("@dcm4che/net/SSLManagerFactory"); + +class DcmQrScp { + device = new Device("dcmqrscp"); + applicationEntity = new ApplicationEntity("*"); + connection = new Connection(); + remoteConnections = {}; + + constructor() { + this.device.addConnectionSync(this.connection); + this.device.addApplicationEntitySync(this.applicationEntity); + this.applicationEntity.setAssociationAcceptorSync(true); + this.applicationEntity.addConnectionSync(this.connection); + this.createDicomServiceRegistry().then(dicomServiceRegistry => { + this.device.setDimseRQHandlerSync(dicomServiceRegistry); + }); + } + + async createDicomServiceRegistry() { + let dicomServiceRegistry = new DicomServiceRegistry(); + + await dicomServiceRegistry.addDicomService(new BasicCEchoSCP()); + + // await dicomServiceRegistry.addDicomService(new JsStgCmtScp(this).get()); + + // #region C-STORE + let jsCStoreScp = new JsCStoreScp(); + await dicomServiceRegistry.addDicomService(jsCStoreScp.get()); + // #endregion + + // #region C-FIND + await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientRootLevel()); + await dicomServiceRegistry.addDicomService(new JsCFindScp().getStudyRootLevel()); + await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientStudyOnlyLevel()); + // #endregion + + // #region C-MOVE + // await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientRootLevel()); + // await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getStudyRootLevel()); + // await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientStudyOnlyLevel()); + // #endregion + + // #region C-GET + // await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientRootLevel()); + // await dicomServiceRegistry.addDicomService(new JsCGetScp().getStudyRootLevel()); + // await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientStudyOnlyLevel()); + // await dicomServiceRegistry.addDicomService(new JsCGetScp().getCompositeLevel()); + // #endregion + + return dicomServiceRegistry; + } + + + async start() { + this.configureConnection(); + this.configureBindServer(); + this.configureTransferCapability(); + this.configureRemoteConnections(); + + + const Executors = importClass("java.util.concurrent.Executors"); + + let executorService = await Executors.newCachedThreadPool(); + let scheduledExecutorService = await Executors.newSingleThreadScheduledExecutor(); + await this.device.setScheduledExecutor(scheduledExecutorService); + await this.device.setExecutor(executorService); + await this.device.bindConnections(); + } + + configureTransferCapability() { + let tc = new TransferCapability( + null, + "*", + TransferCapabilityRole.SCP, + ["*"] + ); + + tc.setQueryOptionsSync(EnumSet.noneOfSync(QueryOption.class)); + this.applicationEntity.addTransferCapabilitySync(tc); + } + + configureBindServer() { + this.connection.setPortSync(raccoonConfig.dicomDimseConfig.port); + this.connection.setHostnameSync(raccoonConfig.dicomDimseConfig.hostname); + this.applicationEntity.setAETitleSync(raccoonConfig.dicomDimseConfig.aeTitle); + } + + configureRemoteConnections() { + let aeFile = path.normalize( + path.join( + __dirname, + "../config/ae-prod.properties" + ) + ); + if (!fileExist.sync(aeFile)) { + aeFile = path.normalize( + path.join( + __dirname, + "../config/ae.properties" + ) + ); + } + let aeConfig = CLIUtils.loadPropertiesSync(aeFile, null); + let itemsSet = aeConfig.entrySetSync(); + let itemsIter = itemsSet.iteratorSync(); + + let item; + while (itemsIter.hasNextSync()) { + item = itemsIter.nextSync(); + /** @type {string} */ + let aet = item.getKeySync(); + /** @type {string} */ + let value = item.getValueSync(); + try { + let hostPortCiphers = value.split(":"); + let ciphers = hostPortCiphers.slice(2); + + let remote = new Connection(); + remote.setHostnameSync(hostPortCiphers[0]); + remote.setPortSync(parseInt(hostPortCiphers[1])); + remote.setTlsCipherSuitesSync(ciphers); + this.remoteConnections[aet] = remote; + } catch (e) { + console.error(e); + throw new (`Invalid entry in ${aeFile}: ${aet}=${value}`); + } + } + } + + /** + * @param {string} dest + */ + getRemoteConnection(dest) { + return _.get(this.remoteConnections, dest); + } + + configureConnection() { + this.connection.setReceivePDULengthSync(raccoonConfig.dicomDimseConfig.maxPduLenRcv); + this.connection.setSendPDULengthSync(raccoonConfig.dicomDimseConfig.maxPduLenSnd); + + if (raccoonConfig.dicomDimseConfig.notAsync) { + this.connection.setMaxOpsInvokedSync(1); + this.connection.setMaxOpsPerformedSync(1); + } else { + this.connection.setMaxOpsInvokedSync(raccoonConfig.dicomDimseConfig.maxOpsInvoked); + this.connection.setMaxOpsPerformedSync(raccoonConfig.dicomDimseConfig.maxOpsPerformed); + } + + this.connection.setPackPDVSync(raccoonConfig.dicomDimseConfig.notPackPdv); + this.connection.setConnectTimeoutSync(raccoonConfig.dicomDimseConfig.connectTimeout); + this.connection.setRequestTimeoutSync(raccoonConfig.dicomDimseConfig.requestTimeout); + this.connection.setAcceptTimeoutSync(raccoonConfig.dicomDimseConfig.acceptTimeout); + this.connection.setReleaseTimeoutSync(raccoonConfig.dicomDimseConfig.releaseTimeout); + this.connection.setSendTimeoutSync(raccoonConfig.dicomDimseConfig.sendTimeout); + this.connection.setStoreTimeoutSync(raccoonConfig.dicomDimseConfig.storeTimeout); + this.connection.setResponseTimeoutSync(raccoonConfig.dicomDimseConfig.responseTimeout); + + if (raccoonConfig.dicomDimseConfig.retrieveTimeout) { + this.connection.setRetrieveTimeoutSync(raccoonConfig.dicomDimseConfig.retrieveTimeout); + this.connection.setRetrieveTimeoutTotalSync(false); + } else if (raccoonConfig.dicomDimseConfig.retrieveTimeoutTotal) { + this.connection.setRetrieveTimeoutTotalSync(raccoonConfig.dicomDimseConfig.retrieveTimeoutTotal); + this.connection.setRetrieveTimeoutSync(false); + } + + this.connection.setIdleTimeoutSync(raccoonConfig.dicomDimseConfig.idleTimeout); + this.connection.setSocketCloseDelaySync(raccoonConfig.dicomDimseConfig.soCloseDelay); + this.connection.setSendBufferSizeSync(raccoonConfig.dicomDimseConfig.soSndBuffer); + this.connection.setReceiveBufferSizeSync(raccoonConfig.dicomDimseConfig.soRcvBuffer); + this.connection.setTcpNoDelaySync(raccoonConfig.dicomDimseConfig.tcpDelay); + this.configureTls(); + } + + configureTls() { + if (!this.configureTlsCipher()) + return; + + if (raccoonConfig.dicomDimseConfig.tls13) { + this.connection.setTlsProtocolsSync(["TLSv1.3"]); + } else if (raccoonConfig.dicomDimseConfig.tls12) { + this.connection.setTlsProtocolsSync(["TLSv1.2"]); + } else if (raccoonConfig.dicomDimseConfig.tls11) { + this.connection.setTlsProtocolsSync(["TLSv1.1"]); + } else if (raccoonConfig.dicomDimseConfig.tls1) { + this.connection.setTlsProtocolsSync(["TLSv1"]); + } else if (raccoonConfig.dicomDimseConfig.ssl3) { + this.connection.setTlsProtocolsSync(["SSLv3"]); + } else if (raccoonConfig.dicomDimseConfig.ssl2Hello) { + this.connection.setTlsProtocolsSync(["SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"]); + } else if (raccoonConfig.dicomDimseConfig.tlsProtocol) { + this.connection.setTlsProtocolsSync([raccoonConfig.dicomDimseConfig.tlsProtocol]); + } + + if (raccoonConfig.dicomDimseConfig.tlsEiaHttps) { + this.connection.setTlsEndpointIdentificationAlgorithmSync(Connection$EndpointIdentificationAlgorithm.HTTPS); + } else if (raccoonConfig.dicomDimseConfig.tlsEiaLdaps) { + this.connection.setTlsEndpointIdentificationAlgorithmSync(Connection$EndpointIdentificationAlgorithm.LDAPS); + } + + this.connection.setTlsNeedClientAuthSync(!raccoonConfig.dicomDimseConfig.tlsNoAuth); + + let device = this.connection.getDeviceSync(); + try { + if (!raccoonConfig.dicomDimseConfig.keyStore) { + device.setKeyManagerSync( + SSLManagerFactory.createKeyManagerSync( + raccoonConfig.dicomDimseConfig.keyStoreType, + raccoonConfig.dicomDimseConfig.keyStore, + raccoonConfig.dicomDimseConfig.keyStorePass, + raccoonConfig.dicomDimseConfig.keyPass + ) + ); + } + device.setTrustManagerSync( + SSLManagerFactory.createTrustManagerSync( + raccoonConfig.dicomDimseConfig.trustStoreType, + raccoonConfig.dicomDimseConfig.trustStore, + raccoonConfig.dicomDimseConfig.trustStorePass + ) + ); + } catch (e) { + throw new Error(e); + } + } + + configureTlsCipher() { + if (raccoonConfig.dicomDimseConfig.tls) { + this.connection.setTlsCipherSuitesSync( + [ + "SSL_RSA_WITH_NULL_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "SSL_RSA_WITH_3DES_EDE_CBC_SHA" + ] + ); + } else if (raccoonConfig.dicomDimseConfig.tlsNull) { + this.connection.setTlsCipherSuitesSync(["SSL_RSA_WITH_NULL_SHA"]); + + } else if (raccoonConfig.dicomDimseConfig.tls3Des) { + this.connection.setTlsCipherSuitesSync(["SSL_RSA_WITH_3DES_EDE_CBC_SHA"]); + + } else if (raccoonConfig.dicomDimseConfig.tlsAes) { + this.connection.setTlsCipherSuitesSync( + [ + "TLS_RSA_WITH_AES_128_CBC_SHA", + "SSL_RSA_WITH_3DES_EDE_CBC_SHA" + ] + ); + } else if (raccoonConfig.dicomDimseConfig.tlsCipher) { + this.connection.setTlsCipherSuitesSync([raccoonConfig.dicomDimseConfig.tlsCipher]); + } + + return this.connection.isTlsSync(); + } +} + +module.exports.DcmQrScp = DcmQrScp; \ No newline at end of file diff --git a/dimse-sql/instanceQueryTask.js b/dimse-sql/instanceQueryTask.js new file mode 100644 index 00000000..29bef741 --- /dev/null +++ b/dimse-sql/instanceQueryTask.js @@ -0,0 +1,148 @@ +const _ = require("lodash"); + +const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); +const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); +const { JsSeriesQueryTask } = require("./seriesQueryTask"); +const { InstanceQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTask"); +const { Attributes } = require("@dcm4che/data/Attributes"); +const { createInstanceQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTaskInject"); +const { InstanceModel } = require("@models/sql/models/instance.model"); +const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); +const { Tag } = require("@dcm4che/data/Tag"); + + +class JsInstanceQueryTask extends JsSeriesQueryTask { + constructor(as, pc, rq, keys) { + super(as, pc, rq, keys); + + this.instanceCursor = null; + this.instance = null; + /** @type { Attributes | null } */ + this.instanceAttr = null; + } + + async get() { + let instanceQueryTask = await InstanceQueryTask.newInstanceAsync( + this.as, + this.pc, + this.rq, + this.keys, + this.getQueryTaskInjectProxy(), + this.getPatientQueryTaskInjectProxy(), + this.getStudyQueryTaskInjectProxy(), + this.getSeriesQueryTaskInjectProxy(), + this.getInstanceQueryTaskInjectProxy() + ); + + await super.get(); + await this.instanceQueryTaskInjectMethods.wrappedFindNextInstance(); + + return instanceQueryTask; + } + + getQueryTaskInjectProxy() { + this.instanceBasicQueryTaskInjectMethods = { + hasMoreMatches: () => { + return !_.isNull(this.instanceAttr); + }, + nextMatch: async () => { + let returnAttr = await Attributes.newInstanceAsync( + await this.patientAttr.size() + await this.studyAttr.size() + await this.seriesAttr.size() + await this.instanceAttr.size() + ); + await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr, this.seriesAttr, this.instanceAttr]); + await returnAttr.addAll(this.patientAttr); + await returnAttr.addAll(this.studyAttr, true); + await returnAttr.addAll(this.seriesAttr, true); + await returnAttr.addAll(this.instanceAttr, true); + + await this.instanceQueryTaskInjectMethods.wrappedFindNextInstance(); + + return returnAttr; + }, + adjust: async (match) => { + return await this.patientAdjust(match); + } + }; + + if (!this.queryTaskInjectProxy) { + this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.instanceBasicQueryTaskInjectMethods); + } + + return this.queryTaskInjectProxy; + } + + getInstanceQueryTaskInjectProxy() { + /** @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTaskInject").InstanceQueryTaskInjectInterface } */ + this.instanceQueryTaskInjectMethods = { + wrappedFindNextInstance: async () => { + await this.instanceQueryTaskInjectMethods.findNextInstance(); + }, + getInstance: async () => { + await this.getNextInstance(); + }, + findNextInstance: async () => { + if (!this.seriesAttr) + return false; + + if (!this.instanceAttr) { + await this.getNextInstanceCursor(); + await this.instanceQueryTaskInjectMethods.getInstance(); + } else { + await this.instanceQueryTaskInjectMethods.getInstance(); + } + + while (!this.instanceAttr && await this.seriesQueryTaskInjectMethods.findNextSeries()) { + await this.getNextInstanceCursor(); + await this.instanceQueryTaskInjectMethods.getInstance(); + } + + return _.isNull(this.instanceAttr); + } + }; + + if (!this.instanceQueryTaskInjectProxy) { + this.instanceQueryTaskInjectProxy = createInstanceQueryTaskInjectProxy(this.instanceQueryTaskInjectMethods); + } + + return this.instanceQueryTaskInjectProxy; + } + + async getNextInstanceCursor() { + this.instanceOffset = 0; + let queryAttr = await Attributes.newInstanceAsync(); + await Attributes.unifyCharacterSets([this.keys, this.patientAttr, this.studyAttr, this.seriesAttr]); + await queryAttr.addAll(this.keys, true); + await queryAttr.addSelected(this.seriesAttr, [Tag.PatientID]); + await queryAttr.addSelected(this.seriesAttr, [Tag.StudyInstanceUID]); + await queryAttr.addSelected(this.seriesAttr, [Tag.SeriesInstanceUID]); + + let queryBuilder = new DimseQueryBuilder(queryAttr, "instance"); + let normalQuery = await queryBuilder.toNormalQuery(); + let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + + let instanceQueryBuilder = new InstanceQueryBuilder({ + query: { + ...sqlQuery + } + }); + let q = instanceQueryBuilder.build(); + this.instanceQuery = { + ...q + }; + } + + async getNextInstance() { + let instance = await InstanceModel.findOne({ + ...this.instanceQuery, + attributes: ["json"], + limit: 1, + offset: this.instanceOffset++ + }); + + this.instance = instance; + this.instanceAttr = this.instance ? await this.instance.getAttributes() : null; + } + +} + +module.exports.JsInstanceQueryTask = JsInstanceQueryTask; \ No newline at end of file diff --git a/dimse-sql/level.js b/dimse-sql/level.js new file mode 100644 index 00000000..83fd89c8 --- /dev/null +++ b/dimse-sql/level.js @@ -0,0 +1,24 @@ +const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); +const { QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); + +const PATIENT_ROOT_LEVELS = EnumSet.ofSync( + QueryRetrieveLevel2.PATIENT, + QueryRetrieveLevel2.STUDY, + QueryRetrieveLevel2.SERIES, + QueryRetrieveLevel2.IMAGE +); + +const STUDY_ROOT_LEVELS = EnumSet.ofSync( + QueryRetrieveLevel2.STUDY, + QueryRetrieveLevel2.SERIES, + QueryRetrieveLevel2.IMAGE +); + +const PATIENT_STUDY_ONLY_LEVELS = EnumSet.ofSync( + QueryRetrieveLevel2.PATIENT, + QueryRetrieveLevel2.STUDY +); + +module.exports.PATIENT_ROOT_LEVELS = PATIENT_ROOT_LEVELS; +module.exports.STUDY_ROOT_LEVELS = STUDY_ROOT_LEVELS; +module.exports.PATIENT_STUDY_ONLY_LEVELS = PATIENT_STUDY_ONLY_LEVELS; \ No newline at end of file diff --git a/dimse-sql/patientQueryTask.js b/dimse-sql/patientQueryTask.js new file mode 100644 index 00000000..b6419a2b --- /dev/null +++ b/dimse-sql/patientQueryTask.js @@ -0,0 +1,172 @@ +const _ = require("lodash"); +const { default: PatientQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTask"); +const { PatientQueryTaskInjectInterface, createPatientQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTaskInject"); +const { createQueryTaskInjectProxy, QueryTaskInjectInterface } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); +const { default: Attributes } = require("@dcm4che/data/Attributes"); +const { default: Tag } = require("@dcm4che/data/Tag"); +const { default: VR } = require("@dcm4che/data/VR"); +const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); +const { Association } = require("@dcm4che/net/Association"); +const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); +const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); +const { PatientModel } = require("@models/sql/models/patient.model"); + + +class JsPatientQueryTask { + constructor(as, pc, rq, keys) { + /** @type { Association } */ + this.as = as; + /** @type { PresentationContext } */ + this.pc = pc; + /** @type { Attributes } */ + this.rq = rq; + /** @type { Attributes } */ + this.keys = keys; + + this.patientAttr = null; + this.offset = 0; + this.query = null; + this.patient = null; + } + + async get() { + let patientQueryTask = await PatientQueryTask.newInstanceAsync( + this.as, + this.pc, + this.rq, + this.keys, + this.getQueryTaskInjectProxy(), + this.getPatientQueryTaskInjectProxy() + ); + + await this.initQuery(); + await this.patientQueryTaskInjectMethods.wrappedFindNextPatient(); + + return patientQueryTask; + } + + getQueryTaskInjectProxy() { + /** @type { QueryTaskInjectInterface } */ + this.patientBasicQueryTaskInjectMethods = { + hasMoreMatches: () => { + return !_.isNull(this.patientAttr); + }, + nextMatch: async () => { + let tempRecord = this.patientAttr; + await this.patientQueryTaskInjectMethods.wrappedFindNextPatient(); + return tempRecord; + }, + adjust: async (match) => { + return this.patientAdjust(match); + } + }; + + if (!this.queryTaskInjectProxy) { + this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.patientBasicQueryTaskInjectMethods); + } + + return this.queryTaskInjectProxy; + } + + getPatientQueryTaskInjectProxy() { + + /** @type { PatientQueryTaskInjectInterface }*/ + this.patientQueryTaskInjectMethods = { + wrappedFindNextPatient: async () => { + await this.patientQueryTaskInjectMethods.findNextPatient(); + }, + getPatient: async () => { + await this.nextPatient(); + }, + findNextPatient: async () => { + await this.patientQueryTaskInjectMethods.getPatient(); + return !_.isNull(this.patientAttr); + } + }; + + if (!this.patientQueryTaskProxy) { + this.patientQueryTaskProxy = createPatientQueryTaskInjectProxy(this.patientQueryTaskInjectMethods); + } + + return this.patientQueryTaskProxy; + } + + /** + * + * @param {Attributes} match + * @returns + */ + async basicAdjust(match) { + if (match == null) { + return null; + } + + let filtered = new Attributes(await match.size()); + + if (!await this.keys.contains(Tag.SpecificCharacterSet)) { + let ss = await match.getStrings(Tag.SpecificCharacterSet); + if (!ss) + await filtered.setString(Tag.SpecificCharacterSet, VR.CS, ss); + } + await filtered.addSelected(match, this.keys); + await filtered.supplementEmpty(this.keys); + return filtered; + } + + async patientAdjust(match) { + let basicAd = await this.basicAdjust(match); + await basicAd.remove(Tag.DirectoryRecordType); + + if (await this.keys.contains(Tag.SOPClassUID)) { + await basicAd.setString(Tag.SOPClassUID, VR.UI, await match.getString(Tag.ReferencedSOPClassUIDInFile)); + } + + if (await this.keys.contains(Tag.SOPInstanceUID)) { + await basicAd.setString(Tag.SOPInstanceUID, VR.UI, await match.getString(Tag.ReferencedSOPInstanceUIDInFile)); + } + + await basicAd.setString(Tag.QueryRetrieveLevel, VR.CS, await this.keys.getString(Tag.QueryRetrieveLevel)); + + return basicAd; + } + + getReturnKeys(query) { + let returnKeys = {}; + let queryKeys = Object.keys(query); + for (let i = 0; i < queryKeys.length; i++) { + returnKeys[queryKeys[i].split(".").shift()] = 1; + } + return returnKeys; + } + + async initQuery() { + this.offset = 0; + let queryBuilder = new DimseQueryBuilder(this.keys, "patient"); + let normalQuery = await queryBuilder.toNormalQuery(); + let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + + let patientQueryBuilder = new PatientQueryBuilder({ + query: { + ...sqlQuery + } + }); + let q = patientQueryBuilder.build(); + this.query = { + ...q + }; + } + + async nextPatient() { + let patient = await PatientModel.findOne({ + ...this.query, + attributes: ["json"], + limit: 1, + offset: this.offset++ + }); + + this.patient = patient; + this.patientAttr = this.patient ? await this.patient.getAttributes() : null; + } +} + +module.exports.JsPatientQueryTask = JsPatientQueryTask; \ No newline at end of file diff --git a/dimse-sql/queryBuilder.js b/dimse-sql/queryBuilder.js new file mode 100644 index 00000000..806f7178 --- /dev/null +++ b/dimse-sql/queryBuilder.js @@ -0,0 +1,26 @@ +const _ = require("lodash"); + +const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { DimseQueryBuilder } = require("@root/dimse/queryBuilder"); + + +class SqlDimseQueryBuilder extends DimseQueryBuilder { + + /** + * + * @param {Attributes} queryKeys + * @param {"patient" | "study" | "series" | "instance"} level + */ + constructor(queryKeys, level="patient") { + super(queryKeys, level); + } + + async getSqlQuery(query) { + return convertAllQueryToDicomTag( + this.cleanEmptyQuery(query) + ); + } +} + +module.exports.SqlDimseQueryBuilder = SqlDimseQueryBuilder; \ No newline at end of file diff --git a/dimse-sql/queryTagsOfEachLevel.js b/dimse-sql/queryTagsOfEachLevel.js new file mode 100644 index 00000000..f6ac4a96 --- /dev/null +++ b/dimse-sql/queryTagsOfEachLevel.js @@ -0,0 +1,33 @@ +const { default: Tag } = require("@dcm4che/data/Tag"); + +const queryTagsOfEachLevel = { + "patient": [ + Tag.PatientName, + Tag.PatientID, + Tag.PatientBirthDate + ], + "study": [ + Tag.PatientID, + Tag.StudyInstanceUID, + Tag.StudyDate, + Tag.StudyTime, + Tag.AccessionNumber + ], + "series": [ + Tag.PatientID, + Tag.StudyInstanceUID, + Tag.SeriesInstanceUID, + Tag.Modality, + Tag.SeriesNumber + ], + "instance": [ + Tag.PatientID, + Tag.StudyInstanceUID, + Tag.SeriesInstanceUID, + Tag.SOPInstanceUID, + Tag.SOPClassUID, + Tag.InstanceNumber + ] +}; + +module.exports.queryTagsOfEachLevel = queryTagsOfEachLevel; \ No newline at end of file diff --git a/dimse-sql/seriesQueryTask.js b/dimse-sql/seriesQueryTask.js new file mode 100644 index 00000000..a7b328f8 --- /dev/null +++ b/dimse-sql/seriesQueryTask.js @@ -0,0 +1,144 @@ +const _ = require("lodash"); + +const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); +const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); +const { JsStudyQueryTask } = require("./studyQueryTask"); +const { SeriesQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTask"); +const { Attributes } = require("@dcm4che/data/Attributes"); +const { createSeriesQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTaskInject"); +const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder"); +const { SeriesModel } = require("@models/sql/models/series.model"); +const { Tag } = require("@dcm4che/data/Tag"); + +class JsSeriesQueryTask extends JsStudyQueryTask { + constructor(as, pc, rq, keys) { + super(as, pc, rq, keys); + + this.seriesCursor = null; + this.series = null; + /** @type { Attributes | null } */ + this.seriesAttr = null; + } + + async get() { + let seriesQueryTask = await SeriesQueryTask.newInstanceAsync( + this.as, + this.pc, + this.rq, + this.keys, + this.getQueryTaskInjectProxy(), + this.getPatientQueryTaskInjectProxy(), + this.getStudyQueryTaskInjectProxy(), + this.getSeriesQueryTaskInjectProxy() + ); + + await super.get(); + await this.seriesQueryTaskInjectMethods.wrappedFindNextSeries(); + + return seriesQueryTask; + } + + getQueryTaskInjectProxy() { + /** @type { QueryTaskInjectInterface } */ + this.seriesBasicQueryTaskInjectMethods = { + hasMoreMatches: () => { + return !_.isNull(this.seriesAttr); + }, + nextMatch: async () => { + let returnAttr = await Attributes.newInstanceAsync( + await this.patientAttr.size() + await this.studyAttr.size() + await this.seriesAttr.size() + ); + await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr, this.seriesAttr]); + await returnAttr.addAll(this.patientAttr); + await returnAttr.addAll(this.studyAttr, true); + await returnAttr.addAll(this.seriesAttr, true); + + await this.seriesQueryTaskInjectMethods.wrappedFindNextSeries(); + + return returnAttr; + }, + adjust: async (match) => { + return await this.patientAdjust(match); + } + }; + + if (!this.queryTaskInjectProxy) { + this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.seriesBasicQueryTaskInjectMethods); + } + + return this.queryTaskInjectProxy; + } + + getSeriesQueryTaskInjectProxy() { + /** @type {import("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTaskInject").SeriesQueryTaskInjectInterface} */ + this.seriesQueryTaskInjectMethods = { + wrappedFindNextSeries: async () => { + await this.seriesQueryTaskInjectMethods.findNextSeries(); + }, + getSeries: async () => { + await this.getNextSeries(); + }, + findNextSeries: async () => { + if (!this.studyAttr) + return false; + + if (!this.seriesAttr) { + await this.getNextSeriesCursor(); + await this.seriesQueryTaskInjectMethods.getSeries(); + } else { + await this.seriesQueryTaskInjectMethods.getSeries(); + } + + while (!this.seriesAttr && await this.studyQueryTaskInjectMethods.findNextStudy()) { + await this.getNextSeriesCursor(); + await this.seriesQueryTaskInjectMethods.getSeries(); + } + + return !_.isNull(this.seriesAttr); + } + }; + + if (!this.seriesQueryTaskInjectProxy) { + this.seriesQueryTaskInjectProxy = createSeriesQueryTaskInjectProxy(this.seriesQueryTaskInjectMethods); + } + + return this.seriesQueryTaskInjectProxy; + } + + async getNextSeriesCursor() { + this.seriesOffset = 0; + let queryAttr = await Attributes.newInstanceAsync(); + await Attributes.unifyCharacterSets([this.keys, this.patientAttr, this.studyAttr]); + await queryAttr.addAll(this.keys, true); + await queryAttr.addSelected(this.studyAttr, [Tag.PatientID]); + await queryAttr.addSelected(this.studyAttr, [Tag.StudyInstanceUID]); + + let queryBuilder = new DimseQueryBuilder(queryAttr, "series"); + let normalQuery = await queryBuilder.toNormalQuery(); + let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + + let seriesQueryBuilder = new SeriesQueryBuilder({ + query: { + ...sqlQuery + } + }); + let q = seriesQueryBuilder.build(); + this.seriesQuery = { + ...q + }; + } + + async getNextSeries() { + let series = await SeriesModel.findOne({ + ...this.seriesQuery, + attributes: ["json"], + limit: 1, + offset: this.seriesOffset++ + }); + + this.series = series; + this.seriesAttr = this.series ? await this.series.getAttributes() : null; + } +} + +module.exports.JsSeriesQueryTask = JsSeriesQueryTask; \ No newline at end of file diff --git a/dimse-sql/stgcmt.js b/dimse-sql/stgcmt.js new file mode 100644 index 00000000..cab983d4 --- /dev/null +++ b/dimse-sql/stgcmt.js @@ -0,0 +1,149 @@ +const path = require("path"); +const { Commands } = require("@dcm4che/net/Commands"); +const { Status } = require("@dcm4che/net/Status"); +const { DicomServiceError } = require("@error/dicom-service"); +const { createStgCmtSCPInjectProxy } = require("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/StgCmtSCPInject"); +const { Attributes } = require("@dcm4che/data/Attributes"); +const { Tag } = require("@dcm4che/data/Tag"); +const { VR } = require("@dcm4che/data/VR"); +const { raccoonConfig } = require("@root/config-class"); +const { findOneInstanceFromKeysAttr } = require("./utils"); +const fileExist = require("@root/utils/file/fileExist"); +const { SimpleStgCmtSCP } = require("@chinlinlee/dcm777/net/SimpleStgCmtSCP"); +const { SendStgCmtResult } = require("@chinlinlee/dcm777/dcmqrscp/SendStgCmtResult"); + +class JsStgCmtScp { + constructor(dcmQrScp) { + /** @type { import("./index").DcmQrScp } */ + this.dcmQrScp = dcmQrScp; + } + + get() { + return new SimpleStgCmtSCP( + this.getStgCmtInjectProxy() + ); + } + + getStgCmtInjectProxy() { + /** @type { import("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/StgCmtSCPInject").StgCmtSCPInjectInterface } */ + const stgCmtInjectProxyMethods = { + onDimseRQ: async (as, pc, dimse, rq, actionInfo) => { + let rsp = await Commands.mkNActionRSP(rq, Status.Success); + let callingAet = await as.getCallingAET(); + let calledAet = await as.getCalledAET(); + + let remoteConnection = this.dcmQrScp.getRemoteConnection(callingAet); + if (!remoteConnection) + throw new DicomServiceError(Status.ProcessingFailure, `Unknown Calling AET: ${callingAet}`); + + let eventInfo; + try { + eventInfo = await this.calculateStorageCommitmentResult(calledAet, actionInfo); + } catch(e) { + console.error(e); + throw e; + } + + try { + await as.writeDimseRSP(pc, rsp, null); + + await this.dcmQrScp.device.execute( + await SendStgCmtResult.newInstanceAsync( + as, + eventInfo, + false, + remoteConnection + ) + ); + } catch(e) { + console.error(`${await as.toString()} << N-ACTION-RSP failed: ${e}`); + } + } + }; + + return createStgCmtSCPInjectProxy(stgCmtInjectProxyMethods, { + keepAsDaemon: true + }); + } + + /** + * + * @param {string} calledAet + * @param {Attributes} actionInfo + * @returns { Promise } + */ + async calculateStorageCommitmentResult(calledAet, actionInfo) { + let requestReq = await actionInfo.getSequence(Tag.ReferencedSOPSequence); + let size = await requestReq.size(); + + let eventInfo = await Attributes.newInstanceAsync(6); + + await eventInfo.setString(Tag.RetrieveAETitle, VR.AE, calledAet); + await eventInfo.setString(Tag.StorageMediaFileSetID, VR.SH, raccoonConfig.mediaStorageID); + await eventInfo.setString(Tag.StorageMediaFileSetUID, VR.SH, raccoonConfig.mediaStorageUID); + await eventInfo.setString(Tag.TransactionUID, VR.UI, await actionInfo.getString(Tag.TransactionUID)); + let successSeq = await eventInfo.newSequence(Tag.ReferencedSOPSequence, size); + let failedSeq = await eventInfo.newSequence(Tag.FailedSOPSequence, size); + + let uidMap = {}; + for (let i = 0; i < size; i++) { + /** @type { Attributes } */ + let item = await requestReq.get(i); + uidMap[await item.getString(Tag.ReferencedSOPInstanceUID)] = await item.getString(Tag.ReferencedSOPClassUID); + } + + for (let key in uidMap) { + let classUid = uidMap[key]; + let attr = await Attributes.newInstanceAsync(); + await attr.setString(Tag.SOPInstanceUID, VR.UI, key); + await attr.setString(Tag.SOPClassUID, VR.UI, classUid); + + let instance = await findOneInstanceFromKeysAttr(attr); + if (instance) { + let isExist = await fileExist( + path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + instance.instancePath + ) + ); + if (isExist) { + await successSeq.add( + await JsStgCmtScp.refSOP(key, classUid, Status.Success) + ); + } else { + await failedSeq.add( + await JsStgCmtScp.refSOP(key, classUid, Status.NoSuchObjectInstance) + ); + } + } else { + await failedSeq.add( + await JsStgCmtScp.refSOP(key, classUid, Status.NoSuchObjectInstance) + ); + } + } + + if (await failedSeq.isEmpty()) + await eventInfo.remove(Tag.FailedSOPSequence); + + return eventInfo; + } + + /** + * @private + * @param {string} instanceUid + * @param {string} classUid + * @param {number} failureReason + * @returns { Promise } + */ + static async refSOP(instanceUid, classUid, failureReason) { + let attr = await Attributes.newInstanceAsync(3); + await attr.setString(Tag.ReferencedSOPClassUID, VR.UI, classUid); + await attr.setString(Tag.ReferencedSOPInstanceUID, VR.UI, instanceUid); + if (failureReason !== Status.Success) { + await attr.setInt(Tag.FailureReason, VR.US, failureReason); + } + return attr; + } +} + +module.exports.JsStgCmtScp = JsStgCmtScp; \ No newline at end of file diff --git a/dimse-sql/studyQueryTask.js b/dimse-sql/studyQueryTask.js new file mode 100644 index 00000000..ca85e314 --- /dev/null +++ b/dimse-sql/studyQueryTask.js @@ -0,0 +1,141 @@ +const _ = require("lodash"); + +const { StudyQueryTask } = require("@chinlinlee/dcm777/net/StudyQueryTask"); +const { JsPatientQueryTask } = require("./patientQueryTask"); +const { default: Tag } = require("@dcm4che/data/Tag"); +const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); +const { StudyQueryTaskInjectInterface, createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); +const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); +const { Attributes } = require("@dcm4che/data/Attributes"); +const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder"); +const { StudyModel } = require("@models/sql/models/study.model"); + +class JsStudyQueryTask extends JsPatientQueryTask { + constructor(as, pc, rq, keys) { + super(as, pc, rq, keys); + + this.studyCursor = false; + this.study = null; + /** @type { Attributes | null } */ + this.studyAttr = null; + } + + async get() { + let studyQueryTask = await StudyQueryTask.newInstanceAsync( + this.as, + this.pc, + this.rq, + this.keys, + this.getQueryTaskInjectProxy(), + this.getPatientQueryTaskInjectProxy(), + this.getStudyQueryTaskInjectProxy() + ); + + await super.get(); + await this.studyQueryTaskInjectMethods.wrappedFindNextStudy(); + + return studyQueryTask; + } + + getQueryTaskInjectProxy() { + /** @type { QueryTaskInjectInterface } */ + this.studyBasicQueryTaskInjectMethods = { + hasMoreMatches: () => { + return !_.isNull(this.studyAttr); + }, + nextMatch: async () => { + let returnAttr = await Attributes.newInstanceAsync( + await this.patientAttr.size() + await this.studyAttr.size() + ); + await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr]); + await returnAttr.addAll(this.patientAttr); + await returnAttr.addAll(this.studyAttr); + + await this.studyQueryTaskInjectMethods.wrappedFindNextStudy(); + + return returnAttr; + }, + adjust: async (match) => { + return await this.patientAdjust(match); + } + }; + + if (!this.queryTaskInjectProxy) { + this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.studyBasicQueryTaskInjectMethods); + } + + return this.queryTaskInjectProxy; + } + + getStudyQueryTaskInjectProxy() { + /** @type { StudyQueryTaskInjectInterface } */ + this.studyQueryTaskInjectMethods = { + wrappedFindNextStudy: async () => { + await this.studyQueryTaskInjectMethods.findNextStudy(); + }, + getStudy: async () => { + await this.getNextStudy(); + }, + findNextStudy: async () => { + if (!this.patientAttr) + return false; + + if (!this.studyAttr) { + await this.getNextStudyCursor(); + await this.studyQueryTaskInjectMethods.getStudy(); + } else { + await this.studyQueryTaskInjectMethods.getStudy(); + } + + while (!this.studyAttr && await this.patientQueryTaskInjectMethods.findNextPatient()) { + await this.getNextStudyCursor(); + await this.studyQueryTaskInjectMethods.getStudy(); + } + + return !_.isNull(this.studyAttr); + } + }; + + if (!this.studyQueryTaskInjectProxy) { + this.studyQueryTaskInjectProxy = createStudyQueryTaskInjectProxy(this.studyQueryTaskInjectMethods); + } + + return this.studyQueryTaskInjectProxy; + } + + async getNextStudyCursor() { + this.studyOffset = 0; + let queryAttr = await Attributes.newInstanceAsync(); + await Attributes.unifyCharacterSets([this.keys, this.patientAttr]); + await queryAttr.addAll(this.keys, true); + await queryAttr.addSelected(this.patientAttr, [Tag.PatientID]); + + let queryBuilder = new DimseQueryBuilder(queryAttr, "study"); + let normalQuery = await queryBuilder.toNormalQuery(); + let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + + let studyQueryBuilder = new StudyQueryBuilder({ + query: { + ...sqlQuery + } + }); + let q = studyQueryBuilder.build(); + this.studyQuery = { + ...q + }; + } + + async getNextStudy() { + let study = await StudyModel.findOne({ + ...this.studyQuery, + attributes: ["json"], + limit: 1, + offset: this.studyOffset++ + }); + + this.study = study; + this.studyAttr = this.study ? await this.study.getAttributes() : null; + } +} + +module.exports.JsStudyQueryTask = JsStudyQueryTask; \ No newline at end of file diff --git a/dimse-sql/utils.js b/dimse-sql/utils.js new file mode 100644 index 00000000..43326547 --- /dev/null +++ b/dimse-sql/utils.js @@ -0,0 +1,100 @@ +const _ = require("lodash"); +const path = require("path"); +const { Attributes } = require("@dcm4che/data/Attributes"); +const mongoose = require("mongoose"); +const { importClass } = require("java-bridge"); +const { raccoonConfig } = require("@root/config-class"); +const { InstanceLocator } = require("@dcm4che/net/service/InstanceLocator"); +const { default: File } = require("@java-wrapper/java/io/File"); +/** + * + * @param {number} tag + */ +function intTagToString(tag) { + return tag.toString(16).padStart(8, "0").toUpperCase(); +} + +/** + * + * @param {Attributes} keys + * @returns + */ +async function getInstancesFromKeysAttr(keys) { + const { DimseQueryBuilder } = require("./queryBuilder"); + let queryBuilder = new DimseQueryBuilder(keys, "instance"); + let normalQuery = await queryBuilder.toNormalQuery(); + let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); + + let returnKeys = { + "instancePath": 1, + "00020010": 1, + "00080016": 1, + "00080018": 1, + "0020000D": 1, + "0020000E": 1 + }; + + let instances = await mongoose.model("dicom").find({ + ...mongoQuery.$match + }, returnKeys).setOptions({ + strictQuery: false + }).exec(); + const JArrayList = await importClass("java.util.ArrayList"); + let list = await JArrayList.newInstanceAsync(); + + for (let instance of instances) { + let instanceFile = await File.newInstanceAsync( + path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + instance.instancePath + ) + ); + + let fileUri = await instanceFile.toURI(); + let fileUriString = await fileUri.toString(); + + let instanceLocator = await InstanceLocator.newInstanceAsync( + _.get(instance, "00080016.Value.0"), + _.get(instance, "00080018.Value.0"), + _.get(instance, "00020010.Value.0"), + fileUriString + ); + + await list.add(instanceLocator); + } + + return list; +} + +/** + * + * @param {Attributes} keys + * @returns + */ +async function findOneInstanceFromKeysAttr(keys) { + const { DimseQueryBuilder } = require("./queryBuilder"); + let queryBuilder = new DimseQueryBuilder(keys, "instance"); + let normalQuery = await queryBuilder.toNormalQuery(); + let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); + + let returnKeys = { + "instancePath": 1, + "00020010": 1, + "00080016": 1, + "00080018": 1, + "0020000D": 1, + "0020000E": 1 + }; + + let instance = await mongoose.model("dicom").findOne({ + ...mongoQuery.$match + }, returnKeys).setOptions({ + strictQuery: false + }).exec(); + + return instance; +} + +module.exports.intTagToString = intTagToString; +module.exports.getInstancesFromKeysAttr = getInstancesFromKeysAttr; +module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr; \ No newline at end of file diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index 2a265ac9..ac82a516 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -10,6 +10,12 @@ const { getStoreDicomFullPath } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + class InstanceModel extends Model { async incrementDeleteStatus() { let deleteStatus = this.getDataValue("deleteStatus"); @@ -189,4 +195,11 @@ InstanceModel.getInstanceOfMedianIndex = async function (query) { return instance; }; +InstanceModel.prototype.getAttributes = async function () { + let seriesObj = this.toJSON(); + + let jsonStr = JSON.stringify(seriesObj.json); + return await Common.getAttributesFromJsonString(jsonStr); +}; + module.exports.InstanceModel = InstanceModel; diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index 29a08a18..eb728dfe 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -2,8 +2,15 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); +const { raccoonConfig } = require("@root/config-class"); -class PatientModel extends Model {}; +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + +class PatientModel extends Model { }; PatientModel.init({ "x00100010": { @@ -66,4 +73,11 @@ PatientModel.getDicomJson = async function (queryOptions) { })); }; +PatientModel.prototype.getAttributes = async function () { + let patientObj = this.toJSON(); + + let jsonStr = JSON.stringify(patientObj.json); + return await Common.getAttributesFromJsonString(jsonStr); +}; + module.exports.PatientModel = PatientModel; diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index d44e1fb8..411a1d9d 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -10,6 +10,12 @@ const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + class SeriesModel extends Model { getSeriesPath() { return this.getDataValue("seriesPath"); @@ -159,4 +165,11 @@ SeriesModel.getPathGroupOfInstances = async function(iParam) { } }; +SeriesModel.prototype.getAttributes = async function () { + let seriesObj = this.toJSON(); + + let jsonStr = JSON.stringify(seriesObj.json); + return await Common.getAttributesFromJsonString(jsonStr); +}; + module.exports.SeriesModel = SeriesModel; diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index 2b77f3e5..d1e5aefd 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -12,6 +12,12 @@ const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + class StudyModel extends Model { async getNumberOfStudyRelatedSeries() { let count = await SeriesModel.count({ @@ -202,4 +208,11 @@ StudyModel.getPathGroupOfInstances = async function (iParam) { } }; +StudyModel.prototype.getAttributes = async function () { + let studyObj = this.toJSON(); + + let jsonStr = JSON.stringify(studyObj.json); + return await Common.getAttributesFromJsonString(jsonStr); +}; + module.exports.StudyModel = StudyModel; diff --git a/package-lock.json b/package-lock.json index a16d493c..f41e40f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -836,6 +836,95 @@ "node": ">=6.9.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@jorgeferrero/stream-to-buffer": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@jorgeferrero/stream-to-buffer/-/stream-to-buffer-2.0.6.tgz", @@ -1416,6 +1505,14 @@ "node": ">=14.0.0" } }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -3348,6 +3445,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3968,9 +4070,9 @@ } }, "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", - "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "engines": { "node": ">=14" }, @@ -4772,11 +4874,11 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "node_modules/jackspeak": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.0.3.tgz", - "integrity": "sha512-0Jud3OMUdMbrlr3PyUMKESq51LXVAB+a239Ywdvd+Kgxj3MaBRml/nVRxf8tQFyfthMjuRkxkv7Vg58pmIMfuQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.0.tgz", + "integrity": "sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg==", "dependencies": { - "cliui": "^7.0.4" + "@isaacs/cliui": "^8.0.2" }, "engines": { "node": ">=14" @@ -4788,55 +4890,29 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jackspeak/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/jackspeak/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/java-bridge": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge/-/java-bridge-2.3.0.tgz", - "integrity": "sha512-qQqooQMY+dyWKbQp67pfc1WZncVNSZFYKf7RQOY2tWYQNBrA4xGsQ8G5VbffXcuW61/Ezja/XrbiHpd3a22Oaw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge/-/java-bridge-2.4.0.tgz", + "integrity": "sha512-KxJCs1DnmxPVol8N6+N7y89u9wfLG5oSkm0xoBBJ7CLEhuuTc/1hiFITjrEEub66AwNZDr2byYB5N2lbUQhrkg==", "dependencies": { - "glob": "^10.0.0" + "glob": "^10.3.3" }, "engines": { "node": ">= 15" }, "optionalDependencies": { - "java-bridge-darwin-arm64": "2.3.0", - "java-bridge-darwin-x64": "2.3.0", - "java-bridge-linux-arm64-gnu": "2.3.0", - "java-bridge-linux-x64-gnu": "2.3.0", - "java-bridge-win32-ia32-msvc": "2.3.0", - "java-bridge-win32-x64-msvc": "2.3.0" + "java-bridge-darwin-arm64": "2.4.0", + "java-bridge-darwin-x64": "2.4.0", + "java-bridge-linux-arm64-gnu": "2.4.0", + "java-bridge-linux-x64-gnu": "2.4.0", + "java-bridge-win32-ia32-msvc": "2.4.0", + "java-bridge-win32-x64-msvc": "2.4.0" } }, "node_modules/java-bridge-darwin-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-darwin-arm64/-/java-bridge-darwin-arm64-2.3.0.tgz", - "integrity": "sha512-jIEly/4h0zZtRv1KA95kg8H4RVp7KUa+rn6bpLupfB/9uVtcM8uf1bFurvZRkGM41JYbM+y7KcotoKTFUgUKXA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-darwin-arm64/-/java-bridge-darwin-arm64-2.4.0.tgz", + "integrity": "sha512-dIFMV6w+lXnOU6xaeOmKsUOloIAzm577mkmi7Cim3Lue2nLZUFhDbwLiHTcnUTbq5+d7Qb8JbkdNTE2AjaPLmg==", "cpu": [ "arm64" ], @@ -4849,9 +4925,9 @@ } }, "node_modules/java-bridge-darwin-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-darwin-x64/-/java-bridge-darwin-x64-2.3.0.tgz", - "integrity": "sha512-ukhU321HSsofgEfnLm9QUbG09U3shwxulRVqIpKIalB+GB+HRF1Op8FzyRJOkA0Jzc0O36uq7sVQ7HBGEtF76A==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-darwin-x64/-/java-bridge-darwin-x64-2.4.0.tgz", + "integrity": "sha512-2Fj9DUynyP6Wt6ceAmeCgkSaUkW20bOtRN/0JTqQe2wwFVGafFxzUjWUYygHlGG26Gtik3kJzic+c7Sxr1lFUQ==", "cpu": [ "x64" ], @@ -4864,9 +4940,9 @@ } }, "node_modules/java-bridge-linux-arm64-gnu": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-linux-arm64-gnu/-/java-bridge-linux-arm64-gnu-2.3.0.tgz", - "integrity": "sha512-ITV+7aFle1ucNhoREOGpem/ez8sCjePKsd+GC/JQ4wBLqNuPhfbvu7qCSHJiB9c/FeRUiLTh9NaBfHyuip92ng==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-linux-arm64-gnu/-/java-bridge-linux-arm64-gnu-2.4.0.tgz", + "integrity": "sha512-5IyZ5t6JDxEgjsOazhS+ZyBFjpSKN2agNFuCuW+R8wQmZMj459kXhsIZEHujtp7MobmMXc1dTcwaV8n3mqaR1w==", "cpu": [ "arm64" ], @@ -4879,9 +4955,9 @@ } }, "node_modules/java-bridge-linux-x64-gnu": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-linux-x64-gnu/-/java-bridge-linux-x64-gnu-2.3.0.tgz", - "integrity": "sha512-Nzk97+9sOR4gMJJPYZd/o9OgkkhuWW5BaxSOmBxAhyZk4O6hha5T2w3QtvrqcfkbpyOY8yeaZdGq3k0n86ALnA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-linux-x64-gnu/-/java-bridge-linux-x64-gnu-2.4.0.tgz", + "integrity": "sha512-9V6kRmEbX1FFGqzRWr+NK9jxf9otiw9wCuLBoLBj/iErAjQpia6l061Z7vydt5c4Yu/lk107oBUo/Hsbtc/syQ==", "cpu": [ "x64" ], @@ -4894,9 +4970,9 @@ } }, "node_modules/java-bridge-win32-ia32-msvc": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-win32-ia32-msvc/-/java-bridge-win32-ia32-msvc-2.3.0.tgz", - "integrity": "sha512-MG4R1OSiCq20a1a/w4Hf5NtnNIulHhc0DnqomS/s09nH+xcVcDV9eQQ12pa7FoCZtK7nkVRwV/xhLQ8wtcK6zg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-win32-ia32-msvc/-/java-bridge-win32-ia32-msvc-2.4.0.tgz", + "integrity": "sha512-D+Im+AAiPgbT0BVWXbuSVQcbek4+VK6SHUVCXF4Gi02Yew/SJ+aNgIIkjP0TMVeYrxG1AlwcYQ80ahuzOm1acA==", "cpu": [ "ia32" ], @@ -4909,9 +4985,9 @@ } }, "node_modules/java-bridge-win32-x64-msvc": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-win32-x64-msvc/-/java-bridge-win32-x64-msvc-2.3.0.tgz", - "integrity": "sha512-dDDD/+plvden+VHA2Zr5DebGDFnO15wpp+Udhn/XZjUBmfDto03BI814IsfTziDv0h/GDZIXVxvAnjtuQyBkGw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-win32-x64-msvc/-/java-bridge-win32-x64-msvc-2.4.0.tgz", + "integrity": "sha512-gK43rdXS6w7pNkz7DDHFnpj4A9v5yu5HPdFU4PankuQzlvLC5r4/S1hBXR4jvI5+md9c8LnvLaxLQwQ+OUfE8A==", "cpu": [ "x64" ], @@ -4932,16 +5008,15 @@ } }, "node_modules/java-bridge/node_modules/glob": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.1.tgz", - "integrity": "sha512-ngom3wq2UhjdbmRE/krgkD8BQyi1KZ5l+D2dVm4+Yj+jJIBp74/ZGunL6gNGc/CYuQmvUBiavWEXIotRiv5R6A==", + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", + "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", "dependencies": { "foreground-child": "^3.1.0", - "fs.realpath": "^1.0.0", "jackspeak": "^2.0.3", - "minimatch": "^9.0.0", - "minipass": "^5.0.0", - "path-scurry": "^1.7.0" + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" }, "bin": { "glob": "dist/cjs/src/bin.js" @@ -4954,9 +5029,9 @@ } }, "node_modules/java-bridge/node_modules/minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4968,11 +5043,11 @@ } }, "node_modules/java-bridge/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/joi": { @@ -6624,12 +6699,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "dependencies": { - "lru-cache": "^9.0.0", - "minipass": "^5.0.0" + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6639,19 +6714,19 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.0.tgz", - "integrity": "sha512-qFXQEwchrZcMVen2uIDceR8Tii6kCJak5rzDStfEM0qA3YLMswaxIEZO0DhIbJ3aqaJiDjt+3crlplOb0tDtKQ==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", "engines": { "node": "14 || >=16.14" } }, "node_modules/path-scurry/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/path-to-regexp": { @@ -8179,6 +8254,20 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-package": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", @@ -8197,6 +8286,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -8768,6 +8869,23 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -9714,6 +9832,64 @@ "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", "dev": true }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@jorgeferrero/stream-to-buffer": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@jorgeferrero/stream-to-buffer/-/stream-to-buffer-2.0.6.tgz", @@ -10188,6 +10364,14 @@ "tslib": "^2.5.0" } }, + "@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "requires": { + "@types/ms": "*" + } + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -11726,6 +11910,11 @@ } } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -12205,9 +12394,9 @@ }, "dependencies": { "signal-exit": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", - "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" } } }, @@ -12808,48 +12997,26 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "jackspeak": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.0.3.tgz", - "integrity": "sha512-0Jud3OMUdMbrlr3PyUMKESq51LXVAB+a239Ywdvd+Kgxj3MaBRml/nVRxf8tQFyfthMjuRkxkv7Vg58pmIMfuQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.0.tgz", + "integrity": "sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg==", "requires": { - "@pkgjs/parseargs": "^0.11.0", - "cliui": "^7.0.4" - }, - "dependencies": { - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" } }, "java-bridge": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge/-/java-bridge-2.3.0.tgz", - "integrity": "sha512-qQqooQMY+dyWKbQp67pfc1WZncVNSZFYKf7RQOY2tWYQNBrA4xGsQ8G5VbffXcuW61/Ezja/XrbiHpd3a22Oaw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge/-/java-bridge-2.4.0.tgz", + "integrity": "sha512-KxJCs1DnmxPVol8N6+N7y89u9wfLG5oSkm0xoBBJ7CLEhuuTc/1hiFITjrEEub66AwNZDr2byYB5N2lbUQhrkg==", "requires": { - "glob": "^10.0.0", - "java-bridge-darwin-arm64": "2.3.0", - "java-bridge-darwin-x64": "2.3.0", - "java-bridge-linux-arm64-gnu": "2.3.0", - "java-bridge-linux-x64-gnu": "2.3.0", - "java-bridge-win32-ia32-msvc": "2.3.0", - "java-bridge-win32-x64-msvc": "2.3.0" + "glob": "^10.3.3", + "java-bridge-darwin-arm64": "2.4.0", + "java-bridge-darwin-x64": "2.4.0", + "java-bridge-linux-arm64-gnu": "2.4.0", + "java-bridge-linux-x64-gnu": "2.4.0", + "java-bridge-win32-ia32-msvc": "2.4.0", + "java-bridge-win32-x64-msvc": "2.4.0" }, "dependencies": { "brace-expansion": { @@ -12861,67 +13028,66 @@ } }, "glob": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.1.tgz", - "integrity": "sha512-ngom3wq2UhjdbmRE/krgkD8BQyi1KZ5l+D2dVm4+Yj+jJIBp74/ZGunL6gNGc/CYuQmvUBiavWEXIotRiv5R6A==", + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", + "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", "requires": { "foreground-child": "^3.1.0", - "fs.realpath": "^1.0.0", "jackspeak": "^2.0.3", - "minimatch": "^9.0.0", - "minipass": "^5.0.0", - "path-scurry": "^1.7.0" + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" } }, "minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "requires": { "brace-expansion": "^2.0.1" } }, "minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==" } } }, "java-bridge-darwin-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-darwin-arm64/-/java-bridge-darwin-arm64-2.3.0.tgz", - "integrity": "sha512-jIEly/4h0zZtRv1KA95kg8H4RVp7KUa+rn6bpLupfB/9uVtcM8uf1bFurvZRkGM41JYbM+y7KcotoKTFUgUKXA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-darwin-arm64/-/java-bridge-darwin-arm64-2.4.0.tgz", + "integrity": "sha512-dIFMV6w+lXnOU6xaeOmKsUOloIAzm577mkmi7Cim3Lue2nLZUFhDbwLiHTcnUTbq5+d7Qb8JbkdNTE2AjaPLmg==", "optional": true }, "java-bridge-darwin-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-darwin-x64/-/java-bridge-darwin-x64-2.3.0.tgz", - "integrity": "sha512-ukhU321HSsofgEfnLm9QUbG09U3shwxulRVqIpKIalB+GB+HRF1Op8FzyRJOkA0Jzc0O36uq7sVQ7HBGEtF76A==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-darwin-x64/-/java-bridge-darwin-x64-2.4.0.tgz", + "integrity": "sha512-2Fj9DUynyP6Wt6ceAmeCgkSaUkW20bOtRN/0JTqQe2wwFVGafFxzUjWUYygHlGG26Gtik3kJzic+c7Sxr1lFUQ==", "optional": true }, "java-bridge-linux-arm64-gnu": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-linux-arm64-gnu/-/java-bridge-linux-arm64-gnu-2.3.0.tgz", - "integrity": "sha512-ITV+7aFle1ucNhoREOGpem/ez8sCjePKsd+GC/JQ4wBLqNuPhfbvu7qCSHJiB9c/FeRUiLTh9NaBfHyuip92ng==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-linux-arm64-gnu/-/java-bridge-linux-arm64-gnu-2.4.0.tgz", + "integrity": "sha512-5IyZ5t6JDxEgjsOazhS+ZyBFjpSKN2agNFuCuW+R8wQmZMj459kXhsIZEHujtp7MobmMXc1dTcwaV8n3mqaR1w==", "optional": true }, "java-bridge-linux-x64-gnu": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-linux-x64-gnu/-/java-bridge-linux-x64-gnu-2.3.0.tgz", - "integrity": "sha512-Nzk97+9sOR4gMJJPYZd/o9OgkkhuWW5BaxSOmBxAhyZk4O6hha5T2w3QtvrqcfkbpyOY8yeaZdGq3k0n86ALnA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-linux-x64-gnu/-/java-bridge-linux-x64-gnu-2.4.0.tgz", + "integrity": "sha512-9V6kRmEbX1FFGqzRWr+NK9jxf9otiw9wCuLBoLBj/iErAjQpia6l061Z7vydt5c4Yu/lk107oBUo/Hsbtc/syQ==", "optional": true }, "java-bridge-win32-ia32-msvc": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-win32-ia32-msvc/-/java-bridge-win32-ia32-msvc-2.3.0.tgz", - "integrity": "sha512-MG4R1OSiCq20a1a/w4Hf5NtnNIulHhc0DnqomS/s09nH+xcVcDV9eQQ12pa7FoCZtK7nkVRwV/xhLQ8wtcK6zg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-win32-ia32-msvc/-/java-bridge-win32-ia32-msvc-2.4.0.tgz", + "integrity": "sha512-D+Im+AAiPgbT0BVWXbuSVQcbek4+VK6SHUVCXF4Gi02Yew/SJ+aNgIIkjP0TMVeYrxG1AlwcYQ80ahuzOm1acA==", "optional": true }, "java-bridge-win32-x64-msvc": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/java-bridge-win32-x64-msvc/-/java-bridge-win32-x64-msvc-2.3.0.tgz", - "integrity": "sha512-dDDD/+plvden+VHA2Zr5DebGDFnO15wpp+Udhn/XZjUBmfDto03BI814IsfTziDv0h/GDZIXVxvAnjtuQyBkGw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/java-bridge-win32-x64-msvc/-/java-bridge-win32-x64-msvc-2.4.0.tgz", + "integrity": "sha512-gK43rdXS6w7pNkz7DDHFnpj4A9v5yu5HPdFU4PankuQzlvLC5r4/S1hBXR4jvI5+md9c8LnvLaxLQwQ+OUfE8A==", "optional": true }, "joi": { @@ -14187,23 +14353,23 @@ "dev": true }, "path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "requires": { - "lru-cache": "^9.0.0", - "minipass": "^5.0.0" + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "dependencies": { "lru-cache": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.0.tgz", - "integrity": "sha512-qFXQEwchrZcMVen2uIDceR8Tii6kCJak5rzDStfEM0qA3YLMswaxIEZO0DhIbJ3aqaJiDjt+3crlplOb0tDtKQ==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==" }, "minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==" } } }, @@ -15354,6 +15520,16 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "stringify-package": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", @@ -15368,6 +15544,14 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -15863,6 +16047,16 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/server.js b/server.js index 6a720695..82716e37 100644 --- a/server.js +++ b/server.js @@ -13,7 +13,7 @@ const sequelizeInstance = require("./models/sql/instance"); const passport = require("passport"); const { raccoonConfig } = require("./config-class"); -const { DcmQrScp } = require('./dimse'); +const { DcmQrScp } = require('./dimse-sql'); require("dotenv"); require("./websocket"); From 9ce42133c2ee13c5c823c7e598dd884742807521 Mon Sep 17 00:00:00 2001 From: chin Date: Wed, 23 Aug 2023 20:53:34 +0800 Subject: [PATCH 080/365] feat: store transfer syntax UID for instance --- models/sql/po/instance.po.js | 1 + 1 file changed, 1 insertion(+) diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 59316694..3fefeb3f 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -9,6 +9,7 @@ const { DicomContentSqModel } = require("../models/dicomContentSQ.model"); const { VerifyIngObserverSqModel } = require("../models/verifyingObserverSQ.model"); const INSTANCE_STORE_TAGS = { + "00020010": true, "00080016": true, "00080018": true, "00080023": true, From 346846950a6f1bf64b3d1ee800ecf175420680c1 Mon Sep 17 00:00:00 2001 From: chin Date: Wed, 23 Aug 2023 20:54:36 +0800 Subject: [PATCH 081/365] feat: C MOVE DIMSE Service --- dimse-sql/c-move.js | 7 ----- dimse-sql/index.js | 14 ++++----- dimse-sql/utils.js | 71 +++++++++++++++++++++++---------------------- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/dimse-sql/c-move.js b/dimse-sql/c-move.js index f3958ed8..c9983402 100644 --- a/dimse-sql/c-move.js +++ b/dimse-sql/c-move.js @@ -1,6 +1,4 @@ const _ = require("lodash"); -const path = require("path"); -const { mongoose } = require("mongoose"); const { Attributes } = require("@dcm4che/data/Attributes"); const { Tag } = require("@dcm4che/data/Tag"); @@ -9,15 +7,10 @@ const { Status } = require("@dcm4che/net/Status"); const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); const { DicomServiceError } = require("@error/dicom-service"); const { createCMoveSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CMoveSCPInject"); -const { DimseQueryBuilder } = require("./queryBuilder"); -const { File } = require("@java-wrapper/java/io/File"); -const { raccoonConfig } = require("@root/config-class"); const { SimpleCMoveSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCMoveSCP"); const { UID } = require("@dcm4che/data/UID"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); -const { importClass } = require("java-bridge"); -const { default: InstanceLocator } = require("@dcm4che/net/service/InstanceLocator"); const { default: AAssociateRQ } = require("@dcm4che/net/pdu/AAssociateRQ"); const { default: Connection } = require("@dcm4che/net/Connection"); const { default: RetrieveTaskImpl } = require("@dcm4che/tool/dcmqrscp/RetrieveTaskImpl"); diff --git a/dimse-sql/index.js b/dimse-sql/index.js index af813de9..f4324e54 100644 --- a/dimse-sql/index.js +++ b/dimse-sql/index.js @@ -59,16 +59,16 @@ class DcmQrScp { // #endregion // #region C-MOVE - // await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientRootLevel()); - // await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getStudyRootLevel()); - // await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientStudyOnlyLevel()); + await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientRootLevel()); + await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getStudyRootLevel()); + await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientStudyOnlyLevel()); // #endregion // #region C-GET - // await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientRootLevel()); - // await dicomServiceRegistry.addDicomService(new JsCGetScp().getStudyRootLevel()); - // await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientStudyOnlyLevel()); - // await dicomServiceRegistry.addDicomService(new JsCGetScp().getCompositeLevel()); + await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientRootLevel()); + await dicomServiceRegistry.addDicomService(new JsCGetScp().getStudyRootLevel()); + await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientStudyOnlyLevel()); + await dicomServiceRegistry.addDicomService(new JsCGetScp().getCompositeLevel()); // #endregion return dicomServiceRegistry; diff --git a/dimse-sql/utils.js b/dimse-sql/utils.js index 43326547..e5f35789 100644 --- a/dimse-sql/utils.js +++ b/dimse-sql/utils.js @@ -6,6 +6,8 @@ const { importClass } = require("java-bridge"); const { raccoonConfig } = require("@root/config-class"); const { InstanceLocator } = require("@dcm4che/net/service/InstanceLocator"); const { default: File } = require("@java-wrapper/java/io/File"); +const sequenceInstance = require("@models/sql/instance"); +const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); /** * * @param {number} tag @@ -20,25 +22,25 @@ function intTagToString(tag) { * @returns */ async function getInstancesFromKeysAttr(keys) { - const { DimseQueryBuilder } = require("./queryBuilder"); + const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); let queryBuilder = new DimseQueryBuilder(keys, "instance"); let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - - let returnKeys = { - "instancePath": 1, - "00020010": 1, - "00080016": 1, - "00080018": 1, - "0020000D": 1, - "0020000E": 1 + let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + let instanceQueryBuilder = new InstanceQueryBuilder({ + query: { + ...sqlQuery + } + }); + let q = instanceQueryBuilder.build(); + let instanceQuery = { + ...q }; - let instances = await mongoose.model("dicom").find({ - ...mongoQuery.$match - }, returnKeys).setOptions({ - strictQuery: false - }).exec(); + let instances = await sequenceInstance.model("Instance").findAll({ + ...instanceQuery, + attributes: ["json", "instancePath"] + }); + const JArrayList = await importClass("java.util.ArrayList"); let list = await JArrayList.newInstanceAsync(); @@ -52,11 +54,11 @@ async function getInstancesFromKeysAttr(keys) { let fileUri = await instanceFile.toURI(); let fileUriString = await fileUri.toString(); - + let instanceLocator = await InstanceLocator.newInstanceAsync( - _.get(instance, "00080016.Value.0"), - _.get(instance, "00080018.Value.0"), - _.get(instance, "00020010.Value.0"), + _.get(instance.json, "00080016.Value.0"), + _.get(instance.json, "00080018.Value.0"), + _.get(instance.json, "00020010.Value.0"), fileUriString ); @@ -72,27 +74,26 @@ async function getInstancesFromKeysAttr(keys) { * @returns */ async function findOneInstanceFromKeysAttr(keys) { - const { DimseQueryBuilder } = require("./queryBuilder"); + const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); let queryBuilder = new DimseQueryBuilder(keys, "instance"); let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - - let returnKeys = { - "instancePath": 1, - "00020010": 1, - "00080016": 1, - "00080018": 1, - "0020000D": 1, - "0020000E": 1 + let sqlQuery = await queryBuilder.getMongoQuery(normalQuery); + let instanceQueryBuilder = new InstanceQueryBuilder({ + query: { + ...sqlQuery + } + }); + let q = instanceQueryBuilder.build(); + let instanceQuery = { + ...q }; - let instance = await mongoose.model("dicom").findOne({ - ...mongoQuery.$match - }, returnKeys).setOptions({ - strictQuery: false - }).exec(); + let instance = await sequenceInstance.model("Instance").findOne({ + ...instanceQuery, + attributes: ["json"] + }); - return instance; + return instance.json; } module.exports.intTagToString = intTagToString; From aa68cf0004740123a5292bcd85988a7add9e9593 Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 24 Aug 2023 19:16:05 +0800 Subject: [PATCH 082/365] refactor: SqlDcmQrScp extend original DcnQrScp --- dimse-sql/index.js | 235 +-------------------------------------------- dimse/c-store.js | 2 - 2 files changed, 4 insertions(+), 233 deletions(-) diff --git a/dimse-sql/index.js b/dimse-sql/index.js index f4324e54..b793d2b6 100644 --- a/dimse-sql/index.js +++ b/dimse-sql/index.js @@ -1,43 +1,17 @@ -const _ = require("lodash"); const { java } = require("@models/DICOM/dcm4che/java-instance"); -const { importClass, appendClasspath, stdout, newProxy } = require("java-bridge"); -const glob = require("glob"); -const path = require("path"); -const { ApplicationEntity } = require("@dcm4che/net/ApplicationEntity"); const { BasicCEchoSCP } = require("@dcm4che/net/service/BasicCEchoSCP"); -const { Connection } = require("@dcm4che/net/Connection"); -const { Device } = require("@dcm4che/net/Device"); const { DicomServiceRegistry } = require("@dcm4che/net/service/DicomServiceRegistry"); -const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); -const { QueryOption } = require("@dcm4che/net/QueryOption"); -const { TransferCapability } = require("@dcm4che/net/TransferCapability"); -const { TransferCapability$Role: TransferCapabilityRole } = require("@dcm4che/net/TransferCapability$Role"); const { JsCStoreScp } = require("./c-store"); const { SqlJsCFindScp: JsCFindScp } = require("./c-find"); -const { default: CLIUtils } = require("@dcm4che/tool/common/CLIUtils"); const { JsCMoveScp } = require("./c-move"); -const fileExist = require("@root/utils/file/fileExist"); const { JsCGetScp } = require("./c-get"); -const { JsStgCmtScp } = require("./stgcmt"); -const { raccoonConfig } = require("@root/config-class"); -const { Connection$EndpointIdentificationAlgorithm } = require("@dcm4che/net/Connection$EndpointIdentificationAlgorithm"); -const { default: SSLManagerFactory } = require("@dcm4che/net/SSLManagerFactory"); +const { DcmQrScp } = require("@root/dimse"); -class DcmQrScp { - device = new Device("dcmqrscp"); - applicationEntity = new ApplicationEntity("*"); - connection = new Connection(); - remoteConnections = {}; +class SqlDcmQrScp extends DcmQrScp { constructor() { - this.device.addConnectionSync(this.connection); - this.device.addApplicationEntitySync(this.applicationEntity); - this.applicationEntity.setAssociationAcceptorSync(true); - this.applicationEntity.addConnectionSync(this.connection); - this.createDicomServiceRegistry().then(dicomServiceRegistry => { - this.device.setDimseRQHandlerSync(dicomServiceRegistry); - }); + super(); } async createDicomServiceRegistry() { @@ -74,207 +48,6 @@ class DcmQrScp { return dicomServiceRegistry; } - - async start() { - this.configureConnection(); - this.configureBindServer(); - this.configureTransferCapability(); - this.configureRemoteConnections(); - - - const Executors = importClass("java.util.concurrent.Executors"); - - let executorService = await Executors.newCachedThreadPool(); - let scheduledExecutorService = await Executors.newSingleThreadScheduledExecutor(); - await this.device.setScheduledExecutor(scheduledExecutorService); - await this.device.setExecutor(executorService); - await this.device.bindConnections(); - } - - configureTransferCapability() { - let tc = new TransferCapability( - null, - "*", - TransferCapabilityRole.SCP, - ["*"] - ); - - tc.setQueryOptionsSync(EnumSet.noneOfSync(QueryOption.class)); - this.applicationEntity.addTransferCapabilitySync(tc); - } - - configureBindServer() { - this.connection.setPortSync(raccoonConfig.dicomDimseConfig.port); - this.connection.setHostnameSync(raccoonConfig.dicomDimseConfig.hostname); - this.applicationEntity.setAETitleSync(raccoonConfig.dicomDimseConfig.aeTitle); - } - - configureRemoteConnections() { - let aeFile = path.normalize( - path.join( - __dirname, - "../config/ae-prod.properties" - ) - ); - if (!fileExist.sync(aeFile)) { - aeFile = path.normalize( - path.join( - __dirname, - "../config/ae.properties" - ) - ); - } - let aeConfig = CLIUtils.loadPropertiesSync(aeFile, null); - let itemsSet = aeConfig.entrySetSync(); - let itemsIter = itemsSet.iteratorSync(); - - let item; - while (itemsIter.hasNextSync()) { - item = itemsIter.nextSync(); - /** @type {string} */ - let aet = item.getKeySync(); - /** @type {string} */ - let value = item.getValueSync(); - try { - let hostPortCiphers = value.split(":"); - let ciphers = hostPortCiphers.slice(2); - - let remote = new Connection(); - remote.setHostnameSync(hostPortCiphers[0]); - remote.setPortSync(parseInt(hostPortCiphers[1])); - remote.setTlsCipherSuitesSync(ciphers); - this.remoteConnections[aet] = remote; - } catch (e) { - console.error(e); - throw new (`Invalid entry in ${aeFile}: ${aet}=${value}`); - } - } - } - - /** - * @param {string} dest - */ - getRemoteConnection(dest) { - return _.get(this.remoteConnections, dest); - } - - configureConnection() { - this.connection.setReceivePDULengthSync(raccoonConfig.dicomDimseConfig.maxPduLenRcv); - this.connection.setSendPDULengthSync(raccoonConfig.dicomDimseConfig.maxPduLenSnd); - - if (raccoonConfig.dicomDimseConfig.notAsync) { - this.connection.setMaxOpsInvokedSync(1); - this.connection.setMaxOpsPerformedSync(1); - } else { - this.connection.setMaxOpsInvokedSync(raccoonConfig.dicomDimseConfig.maxOpsInvoked); - this.connection.setMaxOpsPerformedSync(raccoonConfig.dicomDimseConfig.maxOpsPerformed); - } - - this.connection.setPackPDVSync(raccoonConfig.dicomDimseConfig.notPackPdv); - this.connection.setConnectTimeoutSync(raccoonConfig.dicomDimseConfig.connectTimeout); - this.connection.setRequestTimeoutSync(raccoonConfig.dicomDimseConfig.requestTimeout); - this.connection.setAcceptTimeoutSync(raccoonConfig.dicomDimseConfig.acceptTimeout); - this.connection.setReleaseTimeoutSync(raccoonConfig.dicomDimseConfig.releaseTimeout); - this.connection.setSendTimeoutSync(raccoonConfig.dicomDimseConfig.sendTimeout); - this.connection.setStoreTimeoutSync(raccoonConfig.dicomDimseConfig.storeTimeout); - this.connection.setResponseTimeoutSync(raccoonConfig.dicomDimseConfig.responseTimeout); - - if (raccoonConfig.dicomDimseConfig.retrieveTimeout) { - this.connection.setRetrieveTimeoutSync(raccoonConfig.dicomDimseConfig.retrieveTimeout); - this.connection.setRetrieveTimeoutTotalSync(false); - } else if (raccoonConfig.dicomDimseConfig.retrieveTimeoutTotal) { - this.connection.setRetrieveTimeoutTotalSync(raccoonConfig.dicomDimseConfig.retrieveTimeoutTotal); - this.connection.setRetrieveTimeoutSync(false); - } - - this.connection.setIdleTimeoutSync(raccoonConfig.dicomDimseConfig.idleTimeout); - this.connection.setSocketCloseDelaySync(raccoonConfig.dicomDimseConfig.soCloseDelay); - this.connection.setSendBufferSizeSync(raccoonConfig.dicomDimseConfig.soSndBuffer); - this.connection.setReceiveBufferSizeSync(raccoonConfig.dicomDimseConfig.soRcvBuffer); - this.connection.setTcpNoDelaySync(raccoonConfig.dicomDimseConfig.tcpDelay); - this.configureTls(); - } - - configureTls() { - if (!this.configureTlsCipher()) - return; - - if (raccoonConfig.dicomDimseConfig.tls13) { - this.connection.setTlsProtocolsSync(["TLSv1.3"]); - } else if (raccoonConfig.dicomDimseConfig.tls12) { - this.connection.setTlsProtocolsSync(["TLSv1.2"]); - } else if (raccoonConfig.dicomDimseConfig.tls11) { - this.connection.setTlsProtocolsSync(["TLSv1.1"]); - } else if (raccoonConfig.dicomDimseConfig.tls1) { - this.connection.setTlsProtocolsSync(["TLSv1"]); - } else if (raccoonConfig.dicomDimseConfig.ssl3) { - this.connection.setTlsProtocolsSync(["SSLv3"]); - } else if (raccoonConfig.dicomDimseConfig.ssl2Hello) { - this.connection.setTlsProtocolsSync(["SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"]); - } else if (raccoonConfig.dicomDimseConfig.tlsProtocol) { - this.connection.setTlsProtocolsSync([raccoonConfig.dicomDimseConfig.tlsProtocol]); - } - - if (raccoonConfig.dicomDimseConfig.tlsEiaHttps) { - this.connection.setTlsEndpointIdentificationAlgorithmSync(Connection$EndpointIdentificationAlgorithm.HTTPS); - } else if (raccoonConfig.dicomDimseConfig.tlsEiaLdaps) { - this.connection.setTlsEndpointIdentificationAlgorithmSync(Connection$EndpointIdentificationAlgorithm.LDAPS); - } - - this.connection.setTlsNeedClientAuthSync(!raccoonConfig.dicomDimseConfig.tlsNoAuth); - - let device = this.connection.getDeviceSync(); - try { - if (!raccoonConfig.dicomDimseConfig.keyStore) { - device.setKeyManagerSync( - SSLManagerFactory.createKeyManagerSync( - raccoonConfig.dicomDimseConfig.keyStoreType, - raccoonConfig.dicomDimseConfig.keyStore, - raccoonConfig.dicomDimseConfig.keyStorePass, - raccoonConfig.dicomDimseConfig.keyPass - ) - ); - } - device.setTrustManagerSync( - SSLManagerFactory.createTrustManagerSync( - raccoonConfig.dicomDimseConfig.trustStoreType, - raccoonConfig.dicomDimseConfig.trustStore, - raccoonConfig.dicomDimseConfig.trustStorePass - ) - ); - } catch (e) { - throw new Error(e); - } - } - - configureTlsCipher() { - if (raccoonConfig.dicomDimseConfig.tls) { - this.connection.setTlsCipherSuitesSync( - [ - "SSL_RSA_WITH_NULL_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "SSL_RSA_WITH_3DES_EDE_CBC_SHA" - ] - ); - } else if (raccoonConfig.dicomDimseConfig.tlsNull) { - this.connection.setTlsCipherSuitesSync(["SSL_RSA_WITH_NULL_SHA"]); - - } else if (raccoonConfig.dicomDimseConfig.tls3Des) { - this.connection.setTlsCipherSuitesSync(["SSL_RSA_WITH_3DES_EDE_CBC_SHA"]); - - } else if (raccoonConfig.dicomDimseConfig.tlsAes) { - this.connection.setTlsCipherSuitesSync( - [ - "TLS_RSA_WITH_AES_128_CBC_SHA", - "SSL_RSA_WITH_3DES_EDE_CBC_SHA" - ] - ); - } else if (raccoonConfig.dicomDimseConfig.tlsCipher) { - this.connection.setTlsCipherSuitesSync([raccoonConfig.dicomDimseConfig.tlsCipher]); - } - - return this.connection.isTlsSync(); - } } -module.exports.DcmQrScp = DcmQrScp; \ No newline at end of file +module.exports.DcmQrScp = SqlDcmQrScp; \ No newline at end of file diff --git a/dimse/c-store.js b/dimse/c-store.js index 435d8ed1..bb8c1195 100644 --- a/dimse/c-store.js +++ b/dimse/c-store.js @@ -1,5 +1,3 @@ -const myMongoDB = require("@models/mongodb"); - const path = require("path"); const { createCStoreSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CStoreSCPInject"); const { default: SimpleCStoreSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCStoreSCP"); From 8c060d67c0c8fd44c5d6dc1e483be5beefacb46d Mon Sep 17 00:00:00 2001 From: chin Date: Thu, 24 Aug 2023 19:28:49 +0800 Subject: [PATCH 083/365] fix: delete status overwrite original query # Problems - The `deleteStatus` in following, would overwrite value of `...q` ```js { ...q, attributes: ["json", "x0020000D", "x0020000E", "x00080018"], limit: queryOptions.limit, offset: queryOptions.skip, where: { deleteStatus: 0 } } ``` # Solutions - Every query is compose with [Op.and] in top level - Add condition to check have [Op.and] - and push `deleteStatus` query - or create [Op.and] query for `deleteStatus` --- .../QIDO-RS/service/querybuilder.js | 2 +- models/sql/models/instance.model.js | 20 ++++++++++++++----- models/sql/models/series.model.js | 20 ++++++++++++++----- models/sql/models/study.model.js | 20 ++++++++++++++----- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 428c50f3..64aeb3d5 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -428,7 +428,7 @@ class StudyQueryBuilder extends BaseQueryBuilder { } getPatientID(values) { - return this.getOrQuery(dictionary.keyword.PatientID, values, this.getStringQuery); + return this.getOrQuery(dictionary.keyword.PatientID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); } getStudyDate(values) { diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index ac82a516..0d6f08eb 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -1,6 +1,6 @@ const fsP = require("fs/promises"); const path = require("path"); -const { Sequelize, DataTypes, Model } = require("sequelize"); +const { Sequelize, DataTypes, Model, Op } = require("sequelize"); const _ = require("lodash"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); @@ -108,14 +108,24 @@ InstanceModel.init({ InstanceModel.getDicomJson = async function (queryOptions) { let queryBuilder = new InstanceQueryBuilder(queryOptions); let q = queryBuilder.build(); + if (q[Op.and]) { + q[Op.and].push( + { + deleteStatus: 0 + } + ); + } else { + q[Op.and] = [ + { + deleteStatus: 0 + } + ]; + } let seriesArray = await InstanceModel.findAll({ ...q, attributes: ["json", "x0020000D", "x0020000E", "x00080018"], limit: queryOptions.limit, - offset: queryOptions.skip, - where: { - deleteStatus: 0 - } + offset: queryOptions.skip }); return await Promise.all(seriesArray.map(async series => { diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index 411a1d9d..fcb74252 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -1,6 +1,6 @@ const fsP = require("fs/promises"); const path = require("path"); -const { Sequelize, DataTypes, Model } = require("sequelize"); +const { Sequelize, DataTypes, Model, Op } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder"); @@ -113,14 +113,24 @@ SeriesModel.init({ SeriesModel.getDicomJson = async function(queryOptions) { let queryBuilder = new SeriesQueryBuilder(queryOptions); let q = queryBuilder.build(); + if (q[Op.and]) { + q[Op.and].push( + { + deleteStatus: 0 + } + ); + } else { + q[Op.and] = [ + { + deleteStatus: 0 + } + ]; + } let seriesArray = await SeriesModel.findAll({ ...q, attributes: ["json", "x0020000E"], limit: queryOptions.limit, - offset: queryOptions.skip, - where: { - deleteStatus: 0 - } + offset: queryOptions.skip }); return await Promise.all(seriesArray.map(async series => { diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index d1e5aefd..62c623d1 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -1,6 +1,6 @@ const fsP = require("fs/promises"); const path = require("path"); -const { Sequelize, DataTypes, Model } = require("sequelize"); +const { Sequelize, DataTypes, Model, Op } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { SeriesModel } = require("./series.model"); @@ -141,14 +141,24 @@ StudyModel.updateModalitiesInStudy = async function (study) { StudyModel.getDicomJson = async function (queryOptions) { let queryBuilder = new StudyQueryBuilder(queryOptions); let q = queryBuilder.build(); + if (q[Op.and]) { + q[Op.and].push( + { + deleteStatus: 0 + } + ); + } else { + q[Op.and] = [ + { + deleteStatus: 0 + } + ]; + } let studies = await StudyModel.findAll({ ...q, attributes: ["json"], limit: queryOptions.limit, - offset: queryOptions.skip, - where: { - deleteStatus: 0 - } + offset: queryOptions.skip }); From 97636606b332b7a61617aac8c0e40b5074f11cea Mon Sep 17 00:00:00 2001 From: "CHIN\\a5566" Date: Fri, 1 Sep 2023 14:47:42 +0800 Subject: [PATCH 084/365] refactor: move static methods into schema options --- models/mongodb/models/dicom.js | 303 +++++++++++++-------------- models/mongodb/models/dicomSeries.js | 217 +++++++++---------- models/mongodb/models/dicomStudy.js | 184 ++++++++-------- models/mongodb/models/patient.js | 170 +++++++-------- 4 files changed, 409 insertions(+), 465 deletions(-) diff --git a/models/mongodb/models/dicom.js b/models/mongodb/models/dicom.js index a2cf0df7..af9feed8 100644 --- a/models/mongodb/models/dicom.js +++ b/models/mongodb/models/dicom.js @@ -144,7 +144,7 @@ let dicomModelSchema = new mongoose.Schema( await fsP.unlink( path.join( - raccoonConfig.dicomWebConfig.storeRootPath, + raccoonConfig.dicomWebConfig.storeRootPath, instancePath ) ); @@ -152,7 +152,7 @@ let dicomModelSchema = new mongoose.Schema( console.error(e); } }, - getAttributes: async function() { + getAttributes: async function () { let study = this.toObject(); delete study._id; delete study.id; @@ -166,10 +166,10 @@ let dicomModelSchema = new mongoose.Schema( return mongoose.model("dicom").find(query, keys).setOptions({ strictQuery: false }) - .cursor(); + .cursor(); }, getAuditInstancesInfoFromStudyUID: async (studyUID) => { - let instances = await mongoose.model("dicom").find({studyUID}).exec(); + let instances = await mongoose.model("dicom").find({ studyUID }).exec(); let instanceInfos = { sopClassUIDs: [], @@ -188,11 +188,149 @@ let dicomModelSchema = new mongoose.Schema( patientID ? instanceInfos.patientID = patientID : null; patientName ? instanceInfos.patientName = patientName : null; } - + instanceInfos.sopClassUIDs = _.uniq(instanceInfos.sopClassUIDs); instanceInfos.accessionNumbers = _.uniq(instanceInfos.accessionNumbers); return instanceInfos; + }, + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJson: async function (queryOptions) { + + let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); + let instanceFields = includeFieldsFactory.getInstanceLevelFields(); + + try { + + let docs = await mongoose + .model("dicom") + .find({ + ...queryOptions.query, + deleteStatus: { + $eq: 0 + } + }, { + ...instanceFields + }) + .setOptions({ + strictQuery: false + }) + .limit(queryOptions.limit) + .skip(queryOptions.skip) + .exec(); + + let instanceDicomJson = docs.map(v => { + let obj = v.toObject(); + delete obj._id; + delete obj.id; + obj["00081190"] = { + vr: "UR", + Value: [ + `${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}/instances/${obj["00080018"]["Value"][0]}` + ] + }; + + _.set(obj, dictionary.keyword.RetrieveAETitle, { + ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], + Value: [raccoonConfig.aeTitle] + }); + + return obj; + }); + + return instanceDicomJson; + + } catch (e) { + throw e; + } + + }, + /** + * + * @param {object} iParam + * @param {string} iParam.studyUID + * @param {string} iParam.seriesUID + * @param {string} iParam.instanceUID + */ + getPathOfInstance: async function (iParam) { + let { studyUID, seriesUID, instanceUID } = iParam; + + try { + let query = { + $and: [ + { + studyUID: studyUID + }, + { + seriesUID: seriesUID + }, + { + instanceUID: instanceUID + }, + { + deleteStatus: { + $eq: 0 + } + } + ] + }; + + let doc = await mongoose.model("dicom").findOne(query, { + studyUID: 1, + seriesUID: 1, + instanceUID: 1, + instancePath: 1 + }).exec(); + + if (doc) { + let docObj = doc.toObject(); + docObj.instancePath = getStoreDicomFullPath(docObj); + + return docObj; + } + + return undefined; + + } catch (e) { + throw e; + } + }, + /** + * + * @param {string} studyUID + * @param {string} seriesUID + */ + getInstanceOfMedianIndex: async function (query) { + let instanceCountOfStudy = await mongoose.model("dicom").countDocuments({ + $and: [ + { + studyUID: query.studyUID + }, + { + deleteStatus: { + $eq: 0 + } + } + ] + }); + + return await mongoose.model("dicom").findOne(query, { + studyUID: 1, + seriesUID: 1, + instanceUID: 1, + instancePath: 1 + }) + .sort({ + studyUID: 1, + seriesUID: 1 + }) + .skip(instanceCountOfStudy >> 1) + .limit(1) + .exec(); } }, timestamps: true @@ -405,163 +543,8 @@ async function updateStudyNumberOfStudyRelatedInstance(doc) { } } -/** - * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -dicomModelSchema.statics.getDicomJson = async function (queryOptions) { - - let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); - let instanceFields = includeFieldsFactory.getInstanceLevelFields(); - - try { - - let docs = await mongoose - .model("dicom") - .find({ - ...queryOptions.query, - deleteStatus: { - $eq: 0 - } - }, { - ...instanceFields - }) - .setOptions({ - strictQuery: false - }) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .exec(); - - let instanceDicomJson = docs.map(v => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - obj["00081190"] = { - vr: "UR", - Value: [ - `${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}/instances/${obj["00080018"]["Value"][0]}` - ] - }; - - _.set(obj, dictionary.keyword.RetrieveAETitle, { - ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], - Value: [raccoonConfig.aeTitle] - }); - - return obj; - }); - - return instanceDicomJson; - - } catch (e) { - throw e; - } - -}; - -/** - * - * @param {object} iParam - * @param {string} iParam.studyUID - * @param {string} iParam.seriesUID - * @param {string} iParam.instanceUID - */ -dicomModelSchema.statics.getPathOfInstance = async function (iParam) { - let { studyUID, seriesUID, instanceUID } = iParam; - - try { - let query = { - $and: [ - { - studyUID: studyUID - }, - { - seriesUID: seriesUID - }, - { - instanceUID: instanceUID - }, - { - deleteStatus: { - $eq: 0 - } - } - ] - }; - - let doc = await mongoose.model("dicom").findOne(query, { - studyUID: 1, - seriesUID: 1, - instanceUID: 1, - instancePath: 1 - }).exec(); - - if (doc) { - let docObj = doc.toObject(); - docObj.instancePath = getStoreDicomFullPath(docObj); - - return docObj; - } - - return undefined; - - } catch (e) { - throw e; - } -}; - -/** - * - * @param {string} studyUID - * @param {string} seriesUID - */ -dicomModelSchema.statics.getInstanceOfMedianIndex = async function (query) { - let instanceCountOfStudy = await mongoose.model("dicom").countDocuments({ - $and: [ - { - studyUID: query.studyUID - }, - { - deleteStatus: { - $eq: 0 - } - } - ] - }); - - return await mongoose.model("dicom").findOne(query, { - studyUID: 1, - seriesUID: 1, - instanceUID: 1, - instancePath: 1 - }) - .sort({ - studyUID: 1, - seriesUID: 1 - }) - .skip(instanceCountOfStudy >> 1) - .limit(1) - .exec(); -}; - -/** - * @typedef {import("mongoose").Model & { - * getPathOfInstance: (iParam: { - * studyUID: string, - * seriesUID: string, - * instanceUID: string - * }) => Promise; - * getDicomJson: (queryOptions: import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions) => Promise; - * getInstanceOfMedianIndex: (studyUID: string, seriesUID: string) => Promise; - * }} DicomModelSchema - * - */ - let dicomModel = mongoose.model("dicom", dicomModelSchema, "dicom"); -/** @type {DicomModelSchema} */ module.exports = dicomModel; module.exports.getModalitiesInStudy = getModalitiesInStudy; diff --git a/models/mongodb/models/dicomSeries.js b/models/mongodb/models/dicomSeries.js index 39f81f60..06224f69 100644 --- a/models/mongodb/models/dicomSeries.js +++ b/models/mongodb/models/dicomSeries.js @@ -57,7 +57,7 @@ let dicomSeriesSchema = new mongoose.Schema( recursive: true }); }, - getAttributes: async function() { + getAttributes: async function () { let series = this.toObject(); delete series._id; delete series.id; @@ -71,7 +71,106 @@ let dicomSeriesSchema = new mongoose.Schema( return mongoose.model("dicomSeries").find(query, keys).setOptions({ strictQuery: false }) - .cursor(); + .cursor(); + }, + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJson: async function (queryOptions) { + let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); + let seriesFields = includeFieldsFactory.getSeriesLevelFields(); + + try { + let docs = await mongoose + .model("dicomSeries") + .find({ + ...queryOptions.query, + deleteStatus: { + $eq: 0 + } + }, { + ...seriesFields + }) + .setOptions({ + strictQuery: false + }) + .limit(queryOptions.limit) + .skip(queryOptions.skip) + .exec(); + + + let seriesDicomJson = docs.map((v) => { + let obj = v.toObject(); + delete obj._id; + delete obj.id; + obj["00081190"] = { + vr: "UR", + Value: [ + `${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}` + ] + }; + + _.set(obj, dictionary.keyword.RetrieveAETitle, { + ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], + Value: [raccoonConfig.aeTitle] + }); + + return obj; + }); + + return seriesDicomJson; + + } catch (e) { + throw e; + } + }, + /** + * + * @param {object} iParam + * @param {string} iParam.studyUID + * @param {string} iParam.seriesUID + */ + getPathGroupOfInstances: async function (iParam) { + let { studyUID, seriesUID } = iParam; + try { + let query = [ + { + $match: { + $and: [ + { + seriesUID: seriesUID + }, + { + studyUID: studyUID + } + ] + } + }, + { + $group: { + _id: "$seriesUID", + pathList: { + $addToSet: { + studyUID: "$studyUID", + seriesUID: "$seriesUID", + instanceUID: "$instanceUID", + instancePath: "$instancePath" + } + } + } + } + ]; + let docs = await mongoose.model("dicom").aggregate(query); + let pathGroup = _.get(docs, "0.pathList", []); + + let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); + + return fullPathGroup; + } catch (e) { + throw e; + } } }, timestamps: true @@ -107,120 +206,6 @@ dicomSeriesSchema.index({ "0020000E": 1 }); - -/** - * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -dicomSeriesSchema.statics.getDicomJson = async function (queryOptions) { - let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); - let seriesFields = includeFieldsFactory.getSeriesLevelFields(); - - try { - let docs = await mongoose - .model("dicomSeries") - .find({ - ...queryOptions.query, - deleteStatus: { - $eq: 0 - } - }, { - ...seriesFields - }) - .setOptions({ - strictQuery: false - }) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .exec(); - - - let seriesDicomJson = docs.map((v) => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - obj["00081190"] = { - vr: "UR", - Value: [ - `${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}` - ] - }; - - _.set(obj, dictionary.keyword.RetrieveAETitle, { - ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], - Value: [raccoonConfig.aeTitle] - }); - - return obj; - }); - - return seriesDicomJson; - - } catch (e) { - throw e; - } -}; - -/** - * - * @param {object} iParam - * @param {string} iParam.studyUID - * @param {string} iParam.seriesUID - */ -dicomSeriesSchema.statics.getPathGroupOfInstances = async function (iParam) { - let { studyUID, seriesUID } = iParam; - try { - let query = [ - { - $match: { - $and: [ - { - seriesUID: seriesUID - }, - { - studyUID: studyUID - } - ] - } - }, - { - $group: { - _id: "$seriesUID", - pathList: { - $addToSet: { - studyUID: "$studyUID", - seriesUID: "$seriesUID", - instanceUID: "$instanceUID", - instancePath: "$instancePath" - } - } - } - } - ]; - let docs = await mongoose.model("dicom").aggregate(query); - let pathGroup = _.get(docs, "0.pathList", []); - - let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); - - return fullPathGroup; - } catch (e) { - throw e; - } -}; - - -/** - * @typedef { mongoose.Model & { - * getPathGroupOfInstances: function(iParam: { - * studyUID: string, - * seriesUID: string - * }): Promise; - * getDicomJson: function(queryOptions: import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions): Promise; - * }} DicomSeriesModel -*/ - -/** @type { DicomSeriesModel } */ let dicomSeriesModel = mongoose.model( "dicomSeries", dicomSeriesSchema, diff --git a/models/mongodb/models/dicomStudy.js b/models/mongodb/models/dicomStudy.js index e4e4deb3..d4788c77 100644 --- a/models/mongodb/models/dicomStudy.js +++ b/models/mongodb/models/dicomStudy.js @@ -57,7 +57,7 @@ let dicomStudySchema = new mongoose.Schema( recursive: true }); }, - getAttributes: async function() { + getAttributes: async function () { let study = this.toObject(); delete study._id; delete study.id; @@ -72,6 +72,91 @@ let dicomStudySchema = new mongoose.Schema( strictQuery: false }) .cursor(); + }, + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJson: async function (queryOptions) { + let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); + let studyFields = includeFieldsFactory.getStudyLevelFields(); + + try { + let docs = await mongoose.model("dicomStudy").find({ + ...queryOptions.query, + deleteStatus: { + $eq: 0 + } + }, studyFields) + .limit(queryOptions.limit) + .skip(queryOptions.skip) + .setOptions({ + strictQuery: false + }) + .exec(); + + let studyDicomJson = docs.map((v) => { + let obj = v.toObject(); + delete obj._id; + delete obj.id; + obj["00081190"] = { + vr: "UR", + Value: [`${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}`] + }; + + _.set(obj, dictionary.keyword.RetrieveAETitle, { + ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], + Value: [raccoonConfig.aeTitle] + }); + + return obj; + }); + + return studyDicomJson; + + } catch (e) { + throw e; + } + }, + /** + * + * @param {Object} iParam + * @param {string} iParam.studyUID + */ + getPathGroupOfInstances: async function (iParam) { + let { studyUID } = iParam; + try { + let query = [ + { + $match: { + studyUID: studyUID + } + }, + { + $group: { + _id: "$studyUID", + pathList: { + $addToSet: { + studyUID: "$studyUID", + seriesUID: "$seriesUID", + instanceUID: "$instanceUID", + instancePath: "$instancePath" + } + } + } + } + ]; + let docs = await mongoose.model("dicom").aggregate(query).exec(); + let pathGroup = _.get(docs, "0.pathList", []); + + let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); + + return fullPathGroup; + + } catch (e) { + throw e; + } } }, timestamps: true @@ -93,103 +178,6 @@ dicomStudySchema.index({ "0020000D": 1 }); -/** - * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -dicomStudySchema.statics.getDicomJson = async function (queryOptions) { - let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); - let studyFields = includeFieldsFactory.getStudyLevelFields(); - - try { - let docs = await mongoose.model("dicomStudy").find({ - ...queryOptions.query, - deleteStatus: { - $eq: 0 - } - }, studyFields) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .setOptions({ - strictQuery: false - }) - .exec(); - - let studyDicomJson = docs.map((v) => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - obj["00081190"] = { - vr: "UR", - Value: [`${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}`] - }; - - _.set(obj, dictionary.keyword.RetrieveAETitle, { - ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], - Value: [raccoonConfig.aeTitle] - }); - - return obj; - }); - - return studyDicomJson; - - } catch (e) { - throw e; - } -}; - -/** - * - * @param {Object} iParam - * @param {string} iParam.studyUID - */ -dicomStudySchema.statics.getPathGroupOfInstances = async function (iParam) { - let { studyUID } = iParam; - try { - let query = [ - { - $match: { - studyUID: studyUID - } - }, - { - $group: { - _id: "$studyUID", - pathList: { - $addToSet: { - studyUID: "$studyUID", - seriesUID: "$seriesUID", - instanceUID: "$instanceUID", - instancePath: "$instancePath" - } - } - } - } - ]; - let docs = await mongoose.model("dicom").aggregate(query).exec(); - let pathGroup = _.get(docs, "0.pathList", []); - - let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); - - return fullPathGroup; - - } catch (e) { - throw e; - } -}; - -/** - * @typedef { mongoose.Model & { - * getPathGroupOfInstances: function(iParam: { - * studyUID: string, - * }): Promise; - * getDicomJson: function(queryOptions: import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions): Promise - * }} DicomStudyModel -*/ - -/** @type { DicomStudyModel } */ let dicomStudyModel = mongoose.model( "dicomStudy", dicomStudySchema, diff --git a/models/mongodb/models/patient.js b/models/mongodb/models/patient.js index 5dc47b09..4c945b5f 100644 --- a/models/mongodb/models/patient.js +++ b/models/mongodb/models/patient.js @@ -35,16 +35,8 @@ let patientSchema = new mongoose.Schema( toObject: { getters: true }, - statics: { - getDimseResultCursor : async function (query, keys) { - return mongoose.model("patient").find(query, keys).setOptions({ - strictQuery: false - }) - .cursor(); - } - }, methods: { - getAttributes: async function() { + getAttributes: async function () { let patient = this.toObject(); delete patient._id; delete patient.id; @@ -52,6 +44,84 @@ let patientSchema = new mongoose.Schema( let jsonStr = JSON.stringify(patient); return await Common.getAttributesFromJsonString(jsonStr); } + }, + statics: { + getDimseResultCursor: async function (query, keys) { + return mongoose.model("patient").find(query, keys).setOptions({ + strictQuery: false + }) + .cursor(); + }, + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJson: async function (queryOptions) { + let patientFields = getPatientLevelFields(); + + try { + let docs = await mongoose.model("patient").find(queryOptions.query, patientFields) + .limit(queryOptions.limit) + .skip(queryOptions.skip) + .setOptions({ + strictQuery: false + }) + .exec(); + + + let patientDicomJson = docs.map((v) => { + let obj = v.toObject(); + delete obj._id; + delete obj.id; + return obj; + }); + + return patientDicomJson; + + } catch (e) { + throw e; + } + }, + /** + * + * @param {Object} iParam + * @param {string} iParam.studyUID + */ + getPathGroupOfInstances: async function (iParam) { + let { patientID } = iParam; + try { + let query = [ + { + $match: { + "00100020.Value": patientID + } + }, + { + $group: { + _id: "$studyUID", + pathList: { + $addToSet: { + studyUID: "$studyUID", + seriesUID: "$seriesUID", + instanceUID: "$instanceUID", + instancePath: "$instancePath" + } + } + } + } + ]; + let docs = await mongoose.model("dicom").aggregate(query).exec(); + let pathGroup = _.get(docs, "0.pathList", []); + + let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); + + return fullPathGroup; + + } catch (e) { + throw e; + } + } } } ); @@ -72,37 +142,6 @@ patientSchema.index({ "00100020": 1 }); -/** - * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -patientSchema.statics.getDicomJson = async function (queryOptions) { - let patientFields = getPatientLevelFields(); - - try { - let docs = await mongoose.model("patient").find(queryOptions.query, patientFields) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .setOptions({ - strictQuery: false - }) - .exec(); - - - let patientDicomJson = docs.map((v) => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - return obj; - }); - - return patientDicomJson; - - } catch (e) { - throw e; - } -}; function getPatientLevelFields() { let fields = {}; @@ -112,63 +151,12 @@ function getPatientLevelFields() { return fields; } -/** - * - * @param {Object} iParam - * @param {string} iParam.studyUID - */ -patientSchema.statics.getPathGroupOfInstances = async function (iParam) { - let { patientID } = iParam; - try { - let query = [ - { - $match: { - "00100020.Value": patientID - } - }, - { - $group: { - _id: "$studyUID", - pathList: { - $addToSet: { - studyUID: "$studyUID", - seriesUID: "$seriesUID", - instanceUID: "$instanceUID", - instancePath: "$instancePath" - } - } - } - } - ]; - let docs = await mongoose.model("dicom").aggregate(query).exec(); - let pathGroup = _.get(docs, "0.pathList", []); - - let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); - - return fullPathGroup; - - } catch (e) { - throw e; - } -}; - -/** - * @typedef { mongoose.Model & { - * getPathGroupOfInstances: function(iParam: { - * patientID: string - * }): Promise; - * getDicomJson: function(queryOptions: DicomJsonMongoQueryOptions): Promise - * }} PatientModel -*/ - -/** @type {PatientModel} */ let patientModel = mongoose.model( "patient", patientSchema, "patient" ); -/** @type {PatientModel} */ module.exports = patientModel; module.exports.getPatientLevelFields = getPatientLevelFields; From 90c7c55018ba6e699c7969de082103719ccb5042 Mon Sep 17 00:00:00 2001 From: chin Date: Sat, 16 Sep 2023 08:59:48 +0800 Subject: [PATCH 085/365] refactor: do not expose all error in response --- api-sql/dicom-web/controller/STOW-RS/storeInstance.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api-sql/dicom-web/controller/STOW-RS/storeInstance.js b/api-sql/dicom-web/controller/STOW-RS/storeInstance.js index cec91a7c..80389a74 100644 --- a/api-sql/dicom-web/controller/STOW-RS/storeInstance.js +++ b/api-sql/dicom-web/controller/STOW-RS/storeInstance.js @@ -41,12 +41,13 @@ class StoreInstanceController extends Controller { let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); apiLogger.logger.error(errorStr); - let errorMessage = - errorResponseMessage.getInternalServerErrorMessage(errorStr); this.response.writeHead(500, { "Content-Type": "application/dicom+json" }); - return this.response.end(JSON.stringify(errorMessage)); + return this.response.end(JSON.stringify({ + code: 500, + message: "Server error occurred" + })); } } } From b8376468bc4b03322292c98c849e80641d89a060 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 30 Sep 2023 21:29:44 +0800 Subject: [PATCH 086/365] feat: #13 --- models/sql/models/instance.model.js | 6 ++++++ models/sql/po/instance.po.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index 0d6f08eb..e2ab3fa0 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -58,9 +58,15 @@ InstanceModel.init({ "x00080016": { type: vrTypeMapping.UI }, + "x00080022": { + type: vrTypeMapping.DA + }, "x00080023": { type: vrTypeMapping.DA }, + "x0008002A": { + type: vrTypeMapping.DT + }, "x00080033": { type: vrTypeMapping.TM }, diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 3fefeb3f..cd9d1dba 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -12,7 +12,9 @@ const INSTANCE_STORE_TAGS = { "00020010": true, "00080016": true, "00080018": true, + "00080022": true, "00080023": true, + "0008002A": true, "00080033": true, "00200013": true, "0040A043": true, @@ -62,7 +64,9 @@ class InstancePersistentObject { this.x0020000E = this.series.x0020000E; this.x00080018 = _.get(dicomJson, "00080018.Value.0", undefined); this.x00080016 = _.get(dicomJson, "00080016.Value.0", undefined); + this.x00080022 = _.get(dicomJson, "00080022.Value.0", undefined); this.x00080023 = _.get(dicomJson, "00080023.Value.0", undefined); + this.x0008002A = _.get(dicomJson, "0008002A.Value.0", undefined); this.x00080033 = _.get(dicomJson, "00080033.Value.0", undefined); this.x00200013 = _.get(dicomJson, "00200013.Value.0", undefined); this.x00280008 = _.get(dicomJson, "00280008.Value.0", undefined); @@ -178,7 +182,9 @@ class InstancePersistentObject { x0020000E: this.x0020000E, x00080018: this.x00080018, x00080016: this.x00080016, + x00080022: this.x00080022 ? this.x00080022 : undefined, x00080023: this.x00080023, + x0008002A: this.x0008002A ? this.x0008002A : undefined, x00080033: this.x00080033 ? Number(this.x00080033) : undefined, x00200013: this.x00200013, x00280008: this.x00280008, From 9882b4b6ba26aaa035ef631a365f520424308b2a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 28 Oct 2023 22:11:58 +0800 Subject: [PATCH 087/365] chore: add jscpd config --- .gitignore | 5 ++++- .jscpd.json | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .jscpd.json diff --git a/.gitignore b/.gitignore index 36df3e98..069815de 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ /config/logback.xml /pm2log/** !pm2log/.gitkeep -config/ae-prod.properties \ No newline at end of file +config/ae-prod.properties + +# ignore jscpd output report +/report \ No newline at end of file diff --git a/.jscpd.json b/.jscpd.json new file mode 100644 index 00000000..68acee73 --- /dev/null +++ b/.jscpd.json @@ -0,0 +1,14 @@ +{ + "threshold": 0.1, + "reporters": [ + "html", + "console" + ], + "ignore": [ + "**/node_modules/**", + "models/DICOM/dcm4che/wrapper/**/*.ts", + "models/DICOM/dcm4che/wrapper/**/*.js" + ], + "absolute": true, + "gitignore": true +} \ No newline at end of file From 3d7db77d5b41c00c44c62399590f989128327124 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 29 Oct 2023 08:22:36 +0800 Subject: [PATCH 088/365] refactor: duplicate codes in Thumbnail controller - Extract duplicate part to base thumbnail controller --- .../WADO-RS/thumbnail/base.controller.js | 38 +++++++++++++++++++ .../controller/WADO-RS/thumbnail/frame.js | 38 ++++--------------- .../controller/WADO-RS/thumbnail/instance.js | 35 ++++------------- .../controller/WADO-RS/thumbnail/series.js | 30 +++------------ .../controller/WADO-RS/thumbnail/study.js | 34 +++-------------- 5 files changed, 63 insertions(+), 112 deletions(-) create mode 100644 api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js new file mode 100644 index 00000000..1c73f9d8 --- /dev/null +++ b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js @@ -0,0 +1,38 @@ +const { Controller } = require("@root/api/controller.class"); +const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { ThumbnailService } = require("../service/thumbnail.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); + +class BaseThumbnailController extends Controller { + constructor(req, res) { + super(req, res); + this.factory = StudyImagePathFactory; + this.apiLogger = new ApiLogger(this.request, "WADO-RS"); + this.apiLogger.addTokenValue(); + } + + logAction() { + this.apiLogger.logger.info(`Get Study's Thumbnail [study UID: ${this.request.params.studyUID}]`); + } + + async mainProcess() { + try { + this.logAction(); + let thumbnailService = new ThumbnailService(this.request, this.response, this.apiLogger, this.factory); + return thumbnailService.getThumbnailAndResponse(); + } catch (e) { + let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); + this.apiLogger.logger.error(errorStr); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return this.response.end({ + code: 500, + message: "An exception occurred" + }); + } + } +} + +module.exports.BaseThumbnailController = BaseThumbnailController; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/frame.js b/api/dicom-web/controller/WADO-RS/thumbnail/frame.js index 2354aad0..602be5a8 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/frame.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/frame.js @@ -1,42 +1,18 @@ -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { - ThumbnailService, InstanceThumbnailFactory } = require("../service/thumbnail.service"); +const { BaseThumbnailController } = require("./base.controller"); - - -class RetrieveFrameThumbnailController extends Controller { +class RetrieveFrameThumbnailController extends BaseThumbnailController { constructor(req, res) { super(req, res); + this.factory = InstanceThumbnailFactory; } - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ - series UID: ${this.request.params.seriesUID}]\ - instance UID: ${this.request.params.instanceUID}\ - frames: ${JSON.stringify(this.request.params.frameNumber)}`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, InstanceThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } + logAction() { + this.apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [series UID: ${this.request.params.seriesUID}]\ +instance UID: ${this.request.params.instanceUID}\ +frames: ${JSON.stringify(this.request.params.frameNumber)}`); } } diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/instance.js b/api/dicom-web/controller/WADO-RS/thumbnail/instance.js index 12a6a80c..48ae2241 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/instance.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/instance.js @@ -1,41 +1,20 @@ -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { - ThumbnailService, InstanceThumbnailFactory } = require("../service/thumbnail.service"); +const { BaseThumbnailController } = require("./base.controller"); -class RetrieveInstanceThumbnailController extends Controller { +class RetrieveInstanceThumbnailController extends BaseThumbnailController { constructor(req, res) { super(req, res); + this.factory = InstanceThumbnailFactory; } - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ - series UID: ${this.request.params.seriesUID}]\ - instance UID: ${this.request.params.instanceUID}`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, InstanceThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } + logAction() { + this.apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ +series UID: ${this.request.params.seriesUID}]\ +instance UID: ${this.request.params.instanceUID}`); } } diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/series.js b/api/dicom-web/controller/WADO-RS/thumbnail/series.js index 346ca652..2ac25881 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/series.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/series.js @@ -4,37 +4,19 @@ const { ThumbnailService, SeriesThumbnailFactory } = require("../service/thumbnail.service"); +const { BaseThumbnailController } = require("./base.controller"); -class RetrieveSeriesThumbnailController extends Controller { +class RetrieveSeriesThumbnailController extends BaseThumbnailController { constructor(req, res) { super(req, res); + this.factory = SeriesThumbnailFactory; } - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Series' Thumbnail [study UID: ${this.request.params.studyUID},\ - series UID: ${this.request.params.seriesUID}]`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, SeriesThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } + logAction() { + this.apiLogger.logger.info(`Get Study's Series' Thumbnail [study UID: ${this.request.params.studyUID},\ +series UID: ${this.request.params.seriesUID}]`); } } diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/study.js b/api/dicom-web/controller/WADO-RS/thumbnail/study.js index c9e3899f..6ddcbaf4 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/study.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/study.js @@ -1,39 +1,15 @@ -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { - ThumbnailService, StudyThumbnailFactory } = require("../service/thumbnail.service"); +const { + BaseThumbnailController +} = require("./base.controller"); - -class RetrieveStudyThumbnailController extends Controller { +class RetrieveStudyThumbnailController extends BaseThumbnailController { constructor(req, res) { super(req, res); - } - - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Thumbnail [study UID: ${this.request.params.studyUID}]`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, StudyThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } + this.factory = StudyThumbnailFactory; } } From 1eedd4f5c24e05b7227f57886db90ae4d8314f79 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 29 Oct 2023 10:00:04 +0800 Subject: [PATCH 089/365] refactor: duplicate method `getUidsString` --- .../WADO-RS/service/WADO-RS.service.js | 32 +++++++++++-------- .../WADO-RS/service/thumbnail.service.js | 13 ++------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 184b01a7..2c722aab 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -67,7 +67,7 @@ class ImageMultipartWriter { if (!writeResult.status) { retrieveAuditService.eventResult = EventOutcomeIndicator.MajorFailure; await retrieveAuditService.completedRetrieve(); - + this.response.setHeader("Content-Type", "application/dicom+json"); return this.response.status(writeResult.code).json(writeResult); } @@ -98,7 +98,7 @@ class ImagePathFactory { return { status: false, code: 404, - message: `not found, ${this.getUidsString()}` + message: `not found, ${getUidsString(this.uids)}` }; } @@ -139,16 +139,6 @@ class ImagePathFactory { return existArr; } - getUidsString() { - let uidsKeys = Object.keys(this.uids); - let strArr = []; - for (let i = 0; i < uidsKeys.length; i++) { - let key = uidsKeys[i]; - strArr.push(`${key}: ${this.uids[key]}`); - } - return strArr.join(", "); - } - getPartialImagesPathString() { return JSON.stringify(this.imagePaths.slice(0, 10).map(v => v.instancePath)); } @@ -182,7 +172,7 @@ class InstanceImagePathFactory extends ImagePathFactory { async getImagePaths() { let imagePath = await dicomModel.getPathOfInstance(this.uids); - if(imagePath) + if (imagePath) this.imagePaths = [imagePath]; else this.imagePaths = []; @@ -255,6 +245,21 @@ function addHostnameOfBulkDataUrl(metadata, req) { } } +/** +* +* @param {import("../../../../../utils/typeDef/dicom").Uids} uids +* @returns +*/ +function getUidsString(uids) { + let uidsKeys = Object.keys(uids); + let strArr = []; + for (let i = 0; i < uidsKeys.length; i++) { + let key = uidsKeys[i]; + strArr.push(`${key}: ${uids[key]}`); + } + return strArr.join(", "); +} + module.exports.getAcceptType = getAcceptType; module.exports.supportInstanceMultipartType = supportInstanceMultipartType; module.exports.sendNotSupportedMediaType = sendNotSupportedMediaType; @@ -265,3 +270,4 @@ module.exports.SeriesImagePathFactory = SeriesImagePathFactory; module.exports.InstanceImagePathFactory = InstanceImagePathFactory; module.exports.multipartContentTypeWriter = multipartContentTypeWriter; module.exports.ImageMultipartWriter = ImageMultipartWriter; +module.exports.getUidsString = getUidsString; diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index b7f0b47f..2b629393 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -3,6 +3,7 @@ const dicomSeriesModel = require("../../../../../models/mongodb/models/dicomSeri const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const renderedService = require("../service/rendered.service"); const _ = require("lodash"); +const { getUidsString } = require("./WADO-RS.service"); class ThumbnailService { /** @@ -60,7 +61,7 @@ class ThumbnailService { this.response.writeHead(404, { "Content-Type": "application/dicom+json" }); - let notFoundMessage = errorResponse.getNotFoundErrorMessage(`Not Found, ${this.thumbnailFactory.getUidsString()}`); + let notFoundMessage = errorResponse.getNotFoundErrorMessage(`Not Found, ${getUidsString(this.thumbnailFactory.uids)}`); let notFoundMessageStr = JSON.stringify(notFoundMessage); @@ -82,16 +83,6 @@ class ThumbnailFactory { } async getThumbnailInstance() { } - - getUidsString() { - let uidsKeys = Object.keys(this.uids); - let strArr = []; - for (let i = 0; i < uidsKeys.length; i++) { - let key = uidsKeys[i]; - strArr.push(`${key}: ${this.uids[key]}`); - } - return strArr.join(", "); - } } class StudyThumbnailFactory extends ThumbnailFactory { From 5f1be7da8c0bbbddcdff068f413af14868756edc Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 29 Oct 2023 10:18:18 +0800 Subject: [PATCH 090/365] refactor: remove `writeSpecificFramesRenderedImages` - many codes that same as `writeRenderedImages` --- .../WADO-RS/service/rendered.service.js | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index 4082bce3..e676df4b 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -226,7 +226,7 @@ class InstanceFramesListWriter extends FramesWriter { return this.writeSingleFrame(); } else { let multipartWriter = new MultipartWriter([], this.request, this.response); - await writeSpecificFramesRenderedImages(this.request, frameNumber, this.instanceFramesObj, multipartWriter); + await writeRenderedImages(this.request, frameNumber, this.instanceFramesObj, multipartWriter); multipartWriter.writeFinalBoundary(); return true; } @@ -382,38 +382,18 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { /** * * @param {import("express").Request} req - * @param {number} dicomNumberOfFrames + * @param {number|number[]} dicomNumberOfFrames * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj} instanceFramesObj * @param {import("../../../../../utils/multipartWriter").MultipartWriter} multipartWriter */ async function writeRenderedImages(req, dicomNumberOfFrames, instanceFramesObj, multipartWriter) { try { - for (let i = 0 ; i < dicomNumberOfFrames; i++) { - let postProcessResult = await postProcessFrameImage(req, i+1, instanceFramesObj); - let buffer = postProcessResult.magick.toBuffer(); - multipartWriter.writeBuffer(buffer, { - "Content-Type": "image/jpeg", - "Content-Location": `/dicom-web/studies/${instanceFramesObj.studyUID}/series/${instanceFramesObj.seriesUID}/instances/${instanceFramesObj.instanceUID}/frames/${i+1}/rendered` - }); - } - } catch(e) { - console.error(e); - throw e; - } -} + // Check if dicomNumberOfFrames is an Array, if it is not an Array then convert it to a 0 to N number Array. + let frames = dicomNumberOfFrames; + if (!Array.isArray(frames)) frames = [...Array(frames).keys()]; -/** - * - * @param {import("express").Request} req - * @param {number[]} frames - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj} instanceFramesObj - * @param {import("../../../../../utils/multipartWriter").MultipartWriter} multipartWriter - */ -async function writeSpecificFramesRenderedImages(req, frames, instanceFramesObj, multipartWriter) { - try { for (let i = 0 ; i < frames.length; i++) { - let frameNumber = frames[i]; - let postProcessResult = await postProcessFrameImage(req, frameNumber, instanceFramesObj); + let postProcessResult = await postProcessFrameImage(req, i+1, instanceFramesObj); let buffer = postProcessResult.magick.toBuffer(); multipartWriter.writeBuffer(buffer, { "Content-Type": "image/jpeg", @@ -432,7 +412,6 @@ module.exports.handleViewport = handleViewport; module.exports.getInstanceFrameObj = getInstanceFrameObj; module.exports.postProcessFrameImage = postProcessFrameImage; module.exports.writeRenderedImages = writeRenderedImages; -module.exports.writeSpecificFramesRenderedImages = writeSpecificFramesRenderedImages; module.exports.RenderedImageMultipartWriter = RenderedImageMultipartWriter; module.exports.StudyFramesWriter = StudyFramesWriter; module.exports.SeriesFramesWriter = SeriesFramesWriter; From e870cb1c5221708a2ab94f9b52ed6dbbf76e6a2a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 29 Oct 2023 13:19:20 +0800 Subject: [PATCH 091/365] fix: incorrect frame when retrieve multiple frames # Problems - Related: 3d7db77d5b41c00c44c62399590f989128327124 - we should use number in array, not length (i) - array should be 1 to n, not 0 to n --- .../controller/WADO-RS/service/rendered.service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index e676df4b..ec86feab 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -388,12 +388,13 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { */ async function writeRenderedImages(req, dicomNumberOfFrames, instanceFramesObj, multipartWriter) { try { - // Check if dicomNumberOfFrames is an Array, if it is not an Array then convert it to a 0 to N number Array. + // Check if dicomNumberOfFrames is an Array, if it is not an Array then convert it to a 1 to N number Array. let frames = dicomNumberOfFrames; - if (!Array.isArray(frames)) frames = [...Array(frames).keys()]; + if (!Array.isArray(frames)) frames = [...Array(frames).keys()].map(i => i + 1); for (let i = 0 ; i < frames.length; i++) { - let postProcessResult = await postProcessFrameImage(req, i+1, instanceFramesObj); + let frameNumber = frames[i]; + let postProcessResult = await postProcessFrameImage(req, frameNumber, instanceFramesObj); let buffer = postProcessResult.magick.toBuffer(); multipartWriter.writeBuffer(buffer, { "Content-Type": "image/jpeg", From 367685075647102bf019f5936bd3a5f6d0eb40f8 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 17:12:31 +0800 Subject: [PATCH 092/365] refactor: duplicate codes in retrieve rendered controllers --- .../WADO-RS/rendered/base.controller.js | 77 +++++++++++++++++++ .../WADO-RS/rendered/instanceFrames.js | 60 +++------------ .../controller/WADO-RS/rendered/instances.js | 50 +++--------- .../controller/WADO-RS/rendered/series.js | 46 +++-------- .../controller/WADO-RS/rendered/study.js | 57 +++----------- 5 files changed, 118 insertions(+), 172 deletions(-) create mode 100644 api/dicom-web/controller/WADO-RS/rendered/base.controller.js diff --git a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js new file mode 100644 index 00000000..7a16f24a --- /dev/null +++ b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js @@ -0,0 +1,77 @@ +const _ = require("lodash"); +const renderedService = require("../service/rendered.service"); +const { + StudyImagePathFactory, SeriesImagePathFactory, InstanceImagePathFactory +} = require("../service/WADO-RS.service"); +const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("../../../../../utils/logs/api-logger"); +const { Controller } = require("../../../../controller.class"); + +class BaseRetrieveRenderedController extends Controller { + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + */ + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "WADO-RS"); + this.apiLogger.addTokenValue(); + this.imagePathFactory = StudyImagePathFactory; + this.framesWriter = renderedService.StudyFramesWriter; + } + + logAction() { + throw new Error("abstract, not implement"); + } + + logSuccessful() { + throw new Error("abstract, not implement"); + } + + async mainProcess() { + this.logAction(); + + let headerAccept = _.get(this.request.headers, "accept", ""); + if (!headerAccept == `multipart/related; type="image/jpeg"`) { + let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); + this.response.writeHead(badRequestMessage.HttpStatus, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(badRequestMessage)); + } + + try { + + let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( + this.request, + this.response, + this.imagePathFactory, + this.framesWriter + ); + + let buffer = await renderedImageMultipartWriter.write(); + + this.logSuccessful(); + + if (buffer instanceof Buffer) { + return this.response.end(buffer, "binary"); + } + + return this.response.end(); + } catch(e) { + this.apiLogger.logger.error(e); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end({ + code: 500, + message: "An exception occurred" + }); + } + } +} + + +module.exports.BaseRetrieveRenderedController = BaseRetrieveRenderedController; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js b/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js index 7df99692..d40604ad 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js +++ b/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js @@ -1,61 +1,21 @@ const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { Controller } = require("../../../../controller.class"); +const { BaseRetrieveRenderedController } = require("./base.controller"); +const { InstanceFramesListWriter } = require("../service/rendered.service"); -class RetrieveRenderedInstanceFramesController extends Controller { +class RetrieveRenderedInstanceFramesController extends BaseRetrieveRenderedController { constructor(req, res) { super(req, res); + this.imagePathFactory = InstanceImagePathFactory; + this.framesWriter = InstanceFramesListWriter; } - async mainProcess() { - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - - let { - studyUID, - seriesUID, - instanceUID, - frameNumber - } = this.request.params; - - this.apiLogger.logger.info(`Get study's series' rendered instances' frames, study UID: ${studyUID}, series UID: ${seriesUID}, instance UID: ${instanceUID}, frame: ${frameNumber}`); - - let headerAccept = _.get(this.request.headers, "accept", ""); - if (!headerAccept.includes("*/*") && !headerAccept.includes("image/jpeg")) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow */* or image/jpeg , exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - InstanceImagePathFactory, - renderedService.InstanceFramesListWriter - ); - - let buffer = await renderedImageMultipartWriter.write(); - - this.apiLogger.logger.info(`Get instance's frame successfully, instance UID: ${instanceUID}, frame number: ${JSON.stringify(frameNumber)}`); - - if (buffer instanceof Buffer) { - return this.response.end(buffer, "binary"); - } + logAction() { + this.apiLogger.logger.info(`Get study's series' rendered instances' frames, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}, instance UID: ${this.request.params.instanceUID}, frame: ${this.request.params.frameNumber}`); + } - return this.response.end(); - } catch(e) { - console.error(e); - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e, Object.getOwnPropertyNames(e), 4), "utf8"); - } + logSuccessful() { + this.apiLogger.logger.info(`Get instance's frame successfully, instance UID: ${this.request.params.instanceUID}, frame number: ${JSON.stringify(this.request.params.frameNumber)}`); } } /** diff --git a/api/dicom-web/controller/WADO-RS/rendered/instances.js b/api/dicom-web/controller/WADO-RS/rendered/instances.js index e71810c5..7405906e 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/instances.js +++ b/api/dicom-web/controller/WADO-RS/rendered/instances.js @@ -1,50 +1,20 @@ -const _ = require("lodash"); -const mongoose = require("mongoose"); -const renderedService = require("../service/rendered.service"); const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { Controller } = require("../../../../controller.class"); +const { InstanceFramesWriter } = require("../service/rendered.service"); +const { BaseRetrieveRenderedController } = require("./base.controller"); -class RetrieveRenderedInstancesController extends Controller { +class RetrieveRenderedInstancesController extends BaseRetrieveRenderedController { constructor(req, res) { super(req, res); + this.imagePathFactory = InstanceImagePathFactory; + this.framesWriter = InstanceFramesWriter; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let headerAccept = _.get(this.request.headers, "accept", ""); - - apiLogger.logger.info(`Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); - - if (!headerAccept == `multipart/related; type="image/jpeg"`) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - InstanceImagePathFactory, - renderedService.InstanceFramesWriter - ); + logAction() { + this.apiLogger.logger.info(`Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); + } - await renderedImageMultipartWriter.write(); - - apiLogger.logger.info(`Write Multipart Successfully, study's series' instances' rendered images, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}, instance UID: ${this.request.params.instanceUID}`); - return this.response.end(); - } catch(e) { - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e)); - } + logSuccessful() { + this.apiLogger.logger.info(`Write Multipart Successfully, study's series' instances' rendered images, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}, instance UID: ${this.request.params.instanceUID}`); } } diff --git a/api/dicom-web/controller/WADO-RS/rendered/series.js b/api/dicom-web/controller/WADO-RS/rendered/series.js index bfa027f3..404c5ef6 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/series.js +++ b/api/dicom-web/controller/WADO-RS/rendered/series.js @@ -1,46 +1,20 @@ -const mongoose = require("mongoose"); -const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { logger } = require("../../../../../utils/logs/log"); -const { Controller } = require("../../../../controller.class"); +const { SeriesFramesWriter } = require("../service/rendered.service"); +const { BaseRetrieveRenderedController } = require("./base.controller"); -class RetrieveRenderedSeriesController extends Controller { +class RetrieveRenderedSeriesController extends BaseRetrieveRenderedController { constructor(req, res) { super(req, res); + this.imagePathFactory = SeriesImagePathFactory; + this.framesWriter = SeriesFramesWriter; } - async mainProcess() { - let headerAccept = _.get(this.request.headers, "accept", ""); - logger.info(`[WADO-RS] [Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); - if (!headerAccept == `multipart/related; type="image/jpeg"`) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - SeriesImagePathFactory, - renderedService.SeriesFramesWriter - ); - - await renderedImageMultipartWriter.write(); - - logger.info(`[WADO-RS] [path: ${this.request.originalUrl}] [Write Multipart Successfully, study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); + logAction() { + this.apiLogger.logger.info(`[WADO-RS] [Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); + } - return this.response.end(); - } catch(e) { - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e)); - } + logSuccessful() { + this.apiLogger.logger.info(`[WADO-RS] [path: ${this.request.originalUrl}] [Write Multipart Successfully, study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); } } /** diff --git a/api/dicom-web/controller/WADO-RS/rendered/study.js b/api/dicom-web/controller/WADO-RS/rendered/study.js index 9a8e14f9..004b19da 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/study.js +++ b/api/dicom-web/controller/WADO-RS/rendered/study.js @@ -1,55 +1,20 @@ -const mongoose = require("mongoose"); -const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); -const { - StudyImagePathFactory -} = require("../service/WADO-RS.service"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { Controller } = require("../../../../controller.class"); +const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { StudyFramesWriter } = require("../service/rendered.service"); +const { BaseRetrieveRenderedController } = require("./base.controller"); -class RetrieveRenderedStudyController extends Controller { +class RetrieveRenderedStudyController extends BaseRetrieveRenderedController { constructor(req, res) { super(req, res); + this.imagePathFactory = StudyImagePathFactory; + this.framesWriter = StudyFramesWriter; } - async mainProcess() { - let headerAccept = _.get(this.request.headers, "accept", ""); - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get study's rendered instances, study UID: ${this.request.params.studyUID}`); - - if (!headerAccept == `multipart/related; type="image/jpeg"`) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - StudyImagePathFactory, - renderedService.StudyFramesWriter - ); - - await renderedImageMultipartWriter.write(); - - apiLogger.logger.info(`Write Multipart Successfully, study's rendered instances, study UID: ${this.request.params.studyUID}`); - - return this.response.end(); - } catch(e) { - apiLogger.logger.error(e); + logAction() { + this.apiLogger.logger.info(`Get study's rendered instances, study UID: ${this.request.params.studyUID}`); + } - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e)); - } + logSuccessful() { + this.apiLogger.logger.info(`Write Multipart Successfully, study's rendered instances, study UID: ${this.request.params.studyUID}`); } } /** From ecd8b56758d696413e786d1b2a9f62cb32dd286d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 18:11:37 +0800 Subject: [PATCH 093/365] refactor: duplicate codes in retrieve metadata controllers --- .../WADO-RS/metadata/base.controller.js | 48 +++++++++++++++ .../metadata/retrieveInstanceMetadata.js | 55 ++--------------- .../metadata/retrieveSeriesMetadata.js | 49 ++------------- .../WADO-RS/metadata/retrieveStudyMetadata.js | 59 ++----------------- .../WADO-RS/service/metadata.service.js | 39 ++++++++++++ 5 files changed, 105 insertions(+), 145 deletions(-) create mode 100644 api/dicom-web/controller/WADO-RS/metadata/base.controller.js create mode 100644 api/dicom-web/controller/WADO-RS/service/metadata.service.js diff --git a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js new file mode 100644 index 00000000..0a4f9298 --- /dev/null +++ b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js @@ -0,0 +1,48 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { MetadataService } = require("../service/metadata.service"); + +class BaseRetrieveMetadataController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "WADO-RS"); + this.apiLogger.addTokenValue(); + this.imagePathFactory = StudyImagePathFactory; + } + + logAction() { + throw new Error("Abstract method, not implement"); + } + + async mainProcess() { + this.logAction(); + let metadataService = new MetadataService(this.request, this.imagePathFactory); + + try { + let responseMetadata = await metadataService.getMetadata(this.request.params); + if (responseMetadata.length > 0) { + this.response.writeHead(200, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(responseMetadata)); + } + + this.response.writeHead(204); + return this.response.end(); + + } catch (e) { + this.apiLogger.logger.error(e); + + this.response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + this.response.end({ + code: 500, + message: "An exception occurred" + }); + } + } +} + +module.exports.BaseRetrieveMetadataController = BaseRetrieveMetadataController; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js index eab95795..e5945b4a 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js @@ -1,57 +1,14 @@ -const mongoose = require("mongoose"); -const _ = require("lodash"); -const fs = require("fs"); -const path = require("path"); -const fileExist = require("../../../../../utils/file/fileExist"); -const wadoService = require("../service/WADO-RS.service"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { Controller } = require("../../../../controller.class"); -const dicomModel = require("../../../../../models/mongodb/models/dicom"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); +const { BaseRetrieveMetadataController } = require("./base.controller"); +const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -class RetrieveInstanceMetadataController extends Controller { +class RetrieveInstanceMetadataController extends BaseRetrieveMetadataController { constructor(req, res) { super(req, res); + this.imagePathFactory = InstanceImagePathFactory; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instance Metadata] [instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); - try { - let responseMetadata = []; - - let imagePathObj = await dicomModel.getPathOfInstance(this.request.params); - if (imagePathObj) { - let instanceDir = path.dirname(imagePathObj.instancePath); - let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); - if (await fileExist(metadataPath)) { - let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); - let metadataJson = JSON.parse(metadataJsonStr); - wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); - responseMetadata.push(metadataJson); - } - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(responseMetadata)); - } - - this.response.writeHead(204); - return this.response.end(JSON.stringify( - errorResponse.getNotFoundErrorMessage( - `Not found metadata of instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}` - ) - )); - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - console.error(errorStr); - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - } + logAction() { + this.apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instance Metadata] [instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); } } /** diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js index a340a3d3..e79c2465 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js @@ -3,57 +3,20 @@ const _ = require("lodash"); const fs = require("fs"); const path = require("path"); const fileExist = require("../../../../../utils/file/fileExist"); -const wadoService = require("../service/WADO-RS.service"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const { Controller } = require("../../../../controller.class"); const { ApiLogger } = require("../../../../../utils/logs/api-logger"); +const { BaseRetrieveMetadataController } = require("./base.controller"); +const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); -class RetrieveSeriesMetadataController extends Controller { +class RetrieveSeriesMetadataController extends BaseRetrieveMetadataController { constructor(req, res) { super(req, res); + this.imagePathFactory = SeriesImagePathFactory; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - - apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instances Metadata] [series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); - try { - let responseMetadata = []; - - let imagesPathList = await mongoose.model("dicomSeries").getPathGroupOfInstances(this.request.params); - if (imagesPathList.length > 0) { - for (let imagePathObj of imagesPathList) { - let instanceDir = path.dirname(imagePathObj.instancePath); - let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); - if (await fileExist(metadataPath)) { - let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); - let metadataJson = JSON.parse(metadataJsonStr); - wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); - responseMetadata.push(metadataJson); - } - } - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(responseMetadata)); - } - - this.response.writeHead(204); - return this.response.end(JSON.stringify( - errorResponse.getNotFoundErrorMessage( - `Not found metadata of series UID:${this.request.params.seriesUID} study UID: ${this.request.params.studyUID}` - ) - )); - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - console.error(errorStr); - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - } + logAction() { + this.apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instances Metadata] [series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); } } /** diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js index 63e31620..6c171136 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js @@ -1,61 +1,14 @@ -const mongoose = require("mongoose"); -const _ = require("lodash"); -const fs = require("fs"); -const path = require("path"); -const fileExist = require("../../../../../utils/file/fileExist"); -const wadoService = require("../service/WADO-RS.service"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); +const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { BaseRetrieveMetadataController } = require("./base.controller"); -class RetrieveStudyMetadataController extends Controller { +class RetrieveStudyMetadataController extends BaseRetrieveMetadataController { constructor(req, res) { super(req, res); + this.imagePathFactory = StudyImagePathFactory; } - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Instances Metadata [study UID: ${this.request.params.studyUID}]`); - - try { - let responseMetadata = []; - - let pathGroupOfInstancesInStudy = await mongoose.model("dicomStudy").getPathGroupOfInstances(this.request.params); - - if (pathGroupOfInstancesInStudy.length > 0) { - - for (let imagePathObj of pathGroupOfInstancesInStudy) { - let instanceDir = path.dirname(imagePathObj.instancePath); - let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); - if (await fileExist(metadataPath)) { - let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); - let metadataJson = JSON.parse(metadataJsonStr); - wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); - responseMetadata.push(metadataJson); - } - } - - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(responseMetadata)); - } - - this.response.writeHead(204); - return this.response.end(); - - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - } + logAction() { + this.apiLogger.logger.info(`Get Study's Instances Metadata [study UID: ${this.request.params.studyUID}]`); } } diff --git a/api/dicom-web/controller/WADO-RS/service/metadata.service.js b/api/dicom-web/controller/WADO-RS/service/metadata.service.js new file mode 100644 index 00000000..10321d88 --- /dev/null +++ b/api/dicom-web/controller/WADO-RS/service/metadata.service.js @@ -0,0 +1,39 @@ +const path = require("path"); +const fs = require("fs"); +const fileExist = require("@root/utils/file/fileExist.js"); +const { addHostnameOfBulkDataUrl } = require("./WADO-RS.service.js"); + +class MetadataService { + /** + * @param {import("express").Request} request + * @param {typeof import('./WADO-RS.service.js').ImagePathFactory} imagePathFactory + */ + constructor(request, imagePathFactory) { + this.request = request; + this.imagePathFactory = new imagePathFactory(request.params); + } + + /** + * + * @returns + */ + async getMetadata() { + let metadata = []; + await this.imagePathFactory.getImagePaths(); + for (let imagePathObj of this.imagePathFactory.imagePaths) { + let instanceDir = path.dirname(imagePathObj.instancePath); + let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); + + if (await fileExist(metadataPath)) { + let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); + let metadataJson = JSON.parse(metadataJsonStr); + addHostnameOfBulkDataUrl(metadataJson, this.request); + metadata.push(metadataJson); + } + } + return metadata; + } + +} + +module.exports.MetadataService = MetadataService; \ No newline at end of file From 016a50d8f9b361a2c045b7d34ea0d20d230a2dec Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 18:19:09 +0800 Subject: [PATCH 094/365] refactor: duplicate codes of catching error --- api/controller.class.js | 25 ++++++++++--------- .../WADO-RS/metadata/base.controller.js | 11 ++------ .../WADO-RS/rendered/base.controller.js | 11 ++------ error/controller.handler.js | 22 ++++++++++++++++ 4 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 error/controller.handler.js diff --git a/api/controller.class.js b/api/controller.class.js index 2028cfdb..837ecdfa 100644 --- a/api/controller.class.js +++ b/api/controller.class.js @@ -1,8 +1,9 @@ const { pipe } = require("../utils/pipe.js"); const { pluginGroup, LocalPlugin } = require("../plugins/plugin.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger.js"); class Controller { - + /** * * @param {import('express').Request} req @@ -15,32 +16,32 @@ class Controller { async preProcess() { let currentRouterPlugin = pluginGroup.findLocalPlugin(this.request.originalUrl, this.request.method) || new LocalPlugin(); - + try { - for(let preFn of currentRouterPlugin.preFns) { + for (let preFn of currentRouterPlugin.preFns) { if (this.response.headersSent) break; await preFn(this.request, this.response); } - - } catch(e) { + + } catch (e) { throw new Error(`pre process error in path "${this.request.originalUrl}", ${e.message}`); } - + } - async mainProcess() {} + async mainProcess() { } async postProcess() { let currentRouterPlugin = pluginGroup.findLocalPlugin(this.request.url, this.request.method) || new LocalPlugin(); try { - for(let postFn of currentRouterPlugin.postFns) { + for (let postFn of currentRouterPlugin.postFns) { await postFn(this.request, this.response); } - } catch(e) { + } catch (e) { throw new Error(`post process error in path "${this.request.originalUrl}", ${e.message}`); } } @@ -50,7 +51,7 @@ class Controller { if (this.response.headersSent) return; await this.mainProcess(); - + this.postProcess(); } @@ -60,7 +61,7 @@ class Controller { paramsToString() { let strArr = []; let keys = Object.keys(this.request.params); - for(let i = 0 ; i < keys.length; i++) { + for (let i = 0; i < keys.length; i++) { let key = keys[i]; strArr.push(`${key}: ${this.request.params[key]}`); } @@ -70,7 +71,7 @@ class Controller { queryToString() { let strArr = []; let keys = Object.keys(this.request.query); - for(let i = 0 ; i < keys.length; i++) { + for (let i = 0; i < keys.length; i++) { let key = keys[i]; strArr.push(`${key}: ${this.request.query[key]}`); } diff --git a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js index 0a4f9298..fcacad84 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js @@ -2,6 +2,7 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { StudyImagePathFactory } = require("../service/WADO-RS.service"); const { MetadataService } = require("../service/metadata.service"); +const { ControllerErrorHandler } = require("@error/controller.handler"); class BaseRetrieveMetadataController extends Controller { constructor(req, res) { @@ -32,15 +33,7 @@ class BaseRetrieveMetadataController extends Controller { return this.response.end(); } catch (e) { - this.apiLogger.logger.error(e); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end({ - code: 500, - message: "An exception occurred" - }); + ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); } } } diff --git a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js index 7a16f24a..d0b31825 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js @@ -6,6 +6,7 @@ const { const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { Controller } = require("../../../../controller.class"); +const { ControllerErrorHandler } = require("@error/controller.handler"); class BaseRetrieveRenderedController extends Controller { /** @@ -60,15 +61,7 @@ class BaseRetrieveRenderedController extends Controller { return this.response.end(); } catch(e) { - this.apiLogger.logger.error(e); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end({ - code: 500, - message: "An exception occurred" - }); + ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); } } } diff --git a/error/controller.handler.js b/error/controller.handler.js new file mode 100644 index 00000000..db2a74d1 --- /dev/null +++ b/error/controller.handler.js @@ -0,0 +1,22 @@ +class ControllerErrorHandler { + + /** + * + * @param {Error} e + * @param {ApiLogger} apiLogger + * @param {import("express").Response} response + */ + static raiseInternalServerError(e, apiLogger, response) { + apiLogger.logger.error(e); + + response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + response.end({ + code: 500, + message: "An exception occurred" + }); + } +} + +module.exports.ControllerErrorHandler = ControllerErrorHandler; \ No newline at end of file From abe6014953068aefa62e8f6ac24ba532090a6064 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 18:36:52 +0800 Subject: [PATCH 095/365] refactor: duplicate codes in delete controller --- .../WADO-RS/deletion/base.controller.js | 46 +++++++++++++++++++ .../controller/WADO-RS/deletion/instance.js | 40 ++-------------- .../controller/WADO-RS/deletion/series.js | 40 ++-------------- .../controller/WADO-RS/deletion/study.js | 40 ++-------------- error/controller.handler.js | 8 ++-- 5 files changed, 63 insertions(+), 111 deletions(-) create mode 100644 api/dicom-web/controller/WADO-RS/deletion/base.controller.js diff --git a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js new file mode 100644 index 00000000..d65ec5d3 --- /dev/null +++ b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js @@ -0,0 +1,46 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { DeleteService } = require("./service/delete"); +const { NotFoundInstanceError } = require("@error/dicom-instance"); +const { getNotFoundErrorMessage, getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { ControllerErrorHandler } = require("@error/controller.handler"); + +class BaseDeleteController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "WADO-RS"); + this.apiLogger.addTokenValue(); + this.level = "study"; + } + + async mainProcess() { + let deleteService = new DeleteService(this.request, this.response, this.level); + + try { + await deleteService.delete(); + + return this.response.status(200).json({ + Details: this.getDeleteSuccessfulMessage(), + HttpStatus: 200, + Message: "Delete Successful", + Method: "DELETE" + }); + } catch(e) { + + if (e instanceof NotFoundInstanceError) { + return this.response.status(404).json( + getNotFoundErrorMessage(e.message) + ); + } + + return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + } + } + + getDeleteSuccessfulMessage() { + return `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}`; + } + +} + +module.exports.BaseDeleteController = BaseDeleteController; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/deletion/instance.js b/api/dicom-web/controller/WADO-RS/deletion/instance.js index 5ccd7c2e..ce745014 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/instance.js +++ b/api/dicom-web/controller/WADO-RS/deletion/instance.js @@ -1,43 +1,13 @@ -const mongoose = require("mongoose"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { DeleteService } = require("./service/delete"); -const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { NotFoundInstanceError } = require("../../../../../error/dicom-instance"); +const { BaseDeleteController } = require("./base.controller"); -class DeleteInstanceController extends Controller { +class DeleteInstanceController extends BaseDeleteController { constructor(req, res) { super(req, res); + this.level = "instance"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let deleteService = new DeleteService(this.request, this.response, "instance"); - - try { - await deleteService.delete(); - - return this.response.status(200).json({ - Details: `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}, SOPInstanceUID: ${this.request.params.instanceUID}`, - HttpStatus: 200, - Message: "Delete Successful", - Method: "DELETE" - }); - } catch(e) { - - if (e instanceof NotFoundInstanceError) { - return this.response.status(404).json( - getNotFoundErrorMessage(e.message) - ); - } - - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - + getDeleteSuccessfulMessage() { + `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}, SOPInstanceUID: ${this.request.params.instanceUID}`; } } diff --git a/api/dicom-web/controller/WADO-RS/deletion/series.js b/api/dicom-web/controller/WADO-RS/deletion/series.js index efa106bd..f8d4a440 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/series.js +++ b/api/dicom-web/controller/WADO-RS/deletion/series.js @@ -1,43 +1,13 @@ -const mongoose = require("mongoose"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { DeleteService } = require("./service/delete"); -const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { NotFoundInstanceError } = require("../../../../../error/dicom-instance"); +const { BaseDeleteController } = require("./base.controller"); -class DeleteSeriesController extends Controller { +class DeleteSeriesController extends BaseDeleteController { constructor(req, res) { super(req, res); + this.level = "series"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let deleteService = new DeleteService(this.request, this.response, "series"); - - try { - await deleteService.delete(); - - return this.response.status(200).json({ - Details: `Delete Series permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}`, - HttpStatus: 200, - Message: "Delete Successful", - Method: "DELETE" - }); - } catch(e) { - - if (e instanceof NotFoundInstanceError) { - return this.response.status(404).json( - getNotFoundErrorMessage(e.message) - ); - } - - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - + getDeleteSuccessfulMessage() { + return `Delete Series permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}`; } } diff --git a/api/dicom-web/controller/WADO-RS/deletion/study.js b/api/dicom-web/controller/WADO-RS/deletion/study.js index fe3ca6b2..441f77dd 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/study.js +++ b/api/dicom-web/controller/WADO-RS/deletion/study.js @@ -1,43 +1,9 @@ -const mongoose = require("mongoose"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { DeleteService } = require("./service/delete"); -const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { NotFoundInstanceError } = require("../../../../../error/dicom-instance"); +const { BaseDeleteController } = require("./base.controller"); -class DeleteStudyController extends Controller { +class DeleteStudyController extends BaseDeleteController { constructor(req, res) { super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let deleteService = new DeleteService(this.request, this.response, "study"); - - try { - await deleteService.delete(); - - return this.response.status(200).json({ - Details: `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}`, - HttpStatus: 200, - Message: "Delete Successful", - Method: "DELETE" - }); - } catch(e) { - - if (e instanceof NotFoundInstanceError) { - return this.response.status(404).json( - getNotFoundErrorMessage(e.message) - ); - } - - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - + this.level = "study"; } } diff --git a/error/controller.handler.js b/error/controller.handler.js index db2a74d1..145c8f95 100644 --- a/error/controller.handler.js +++ b/error/controller.handler.js @@ -1,3 +1,5 @@ +const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); + class ControllerErrorHandler { /** @@ -12,10 +14,8 @@ class ControllerErrorHandler { response.writeHead(500, { "Content-Type": "application/dicom+json" }); - response.end({ - code: 500, - message: "An exception occurred" - }); + + return response.json(getInternalServerErrorMessage("An exception occurred")); } } From 530e148bf3206059af58497aac65d41261c27170 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 20:10:23 +0800 Subject: [PATCH 096/365] refactor: duplicate codes in bulkdata controllers --- .jscpd.json | 3 +- .../WADO-RS/bulkdata/base.controller.js | 48 +++++++ .../controller/WADO-RS/bulkdata/bulkdata.js | 28 ++-- .../controller/WADO-RS/bulkdata/instance.js | 46 ++----- .../controller/WADO-RS/bulkdata/series.js | 48 ++----- .../WADO-RS/bulkdata/service/bulkdata.js | 120 +++++++++++++----- .../controller/WADO-RS/bulkdata/study.js | 47 ++----- error/controller.handler.js | 12 +- 8 files changed, 185 insertions(+), 167 deletions(-) create mode 100644 api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js diff --git a/.jscpd.json b/.jscpd.json index 68acee73..ed62b12d 100644 --- a/.jscpd.json +++ b/.jscpd.json @@ -7,7 +7,8 @@ "ignore": [ "**/node_modules/**", "models/DICOM/dcm4che/wrapper/**/*.ts", - "models/DICOM/dcm4che/wrapper/**/*.js" + "models/DICOM/dcm4che/wrapper/**/*.js", + "docs/**" ], "absolute": true, "gitignore": true diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js new file mode 100644 index 00000000..646ba1cd --- /dev/null +++ b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js @@ -0,0 +1,48 @@ +const { Controller } = require("@root/api/controller.class"); +const { StudyBulkDataFactory, BulkDataService } = require("./service/bulkdata"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { ControllerErrorHandler } = require("@error/controller.handler"); + +class BaseBulkDataController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "WADO-RS"); + this.apiLogger.addTokenValue(); + this.bulkDataFactoryType = StudyBulkDataFactory; + this.imagePathFactoryType = StudyImagePathFactory; + } + + logAction() { + throw new Error("Abstract Method not implemented."); + } + + async mainProcess() { + this.logAction(); + + let bulkDataService = new BulkDataService(this.request, this.response, this.bulkDataFactoryType); + + try { + let bulkDataArray = await bulkDataService.getBulkData(); + for(let bulkData of bulkDataArray) { + await bulkDataService.writeBulkData(bulkData); + } + + let imagePathFactory = new this.imagePathFactoryType({ + ...this.request.params + }); + await imagePathFactory.getImagePaths(); + + for(let imagePathObj of imagePathFactory.imagePaths) { + await bulkDataService.writeBulkData(imagePathObj); + } + + bulkDataService.multipartWriter.writeFinalBoundary(); + return this.response.end(); + } catch(e) { + return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + } + } +} + +module.exports.BaseBulkDataController = BaseBulkDataController; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js index 2c2fa6bf..a199fa29 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js @@ -1,35 +1,37 @@ const mongoose = require("mongoose"); const { Controller } = require("../../../../controller.class"); const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); +const { BulkDataService, SpecificBulkDataFactory } = require("./service/bulkdata"); const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); +const { BaseBulkDataController } = require("./base.controller"); +const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); +const { ControllerErrorHandler } = require("@error/controller.handler"); -class BulkDataController extends Controller { +class BulkDataController extends BaseBulkDataController { constructor(req, res) { super(req, res); + this.bulkDataFactoryType = SpecificBulkDataFactory; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data ${this.request.params.binaryValuePath}\ + logAction() { + this.apiLogger.logger.info(`Get bulk data ${this.request.params.binaryValuePath}\ , from StudyInstanceUID: ${this.request.params.studyUID}\ , SeriesInstanceUID: ${this.request.params.seriesUID}\ , SOPInstanceUID: ${this.request.params.instanceUID}`); + } + + async mainProcess() { + this.logAction(); - let bulkDataService = new BulkDataService(this.request, this.response); + let bulkDataService = new BulkDataService(this.request, this.response, this.bulkDataFactoryType); try { - let bulkData = await bulkDataService.getSpecificBulkData(); + let bulkData = await bulkDataService.getBulkData(); await bulkDataService.writeBulkData(bulkData); bulkDataService.multipartWriter.writeFinalBoundary(); return this.response.end(); } catch(e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); + return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); } } diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js index 67ba0077..dc51e844 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js @@ -1,49 +1,23 @@ -const mongoose = require("mongoose"); -const { Controller } = require("../../../../controller.class"); const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); +const { BulkDataService, InstanceBulkDataFactory } = require("./service/bulkdata"); const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); const dicomModel = require("../../../../../models/mongodb/models/dicom"); +const { BaseBulkDataController } = require("./base.controller"); +const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -class InstanceBulkDataController extends Controller { +class InstanceBulkDataController extends BaseBulkDataController { constructor(req, res) { super(req, res); + this.bulkDataFactoryType = InstanceBulkDataFactory; + this.imagePathFactoryType = InstanceImagePathFactory; } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ + + logAction() { + this.apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ , SeriesInstanceUID: ${this.request.params.seriesUID}\ , SOPInstanceUID: ${this.request.params.instanceUID}`); - - let bulkDataService = new BulkDataService(this.request, this.response); - - try { - let bulkDataArray = await bulkDataService.getInstanceBulkData(); - for (let bulkData of bulkDataArray) { - await bulkDataService.writeBulkData(bulkData); - } - - let dicomInstancePathObj = await dicomModel.getPathOfInstance({ - studyUID: this.request.params.studyUID, - seriesUID: this.request.params.seriesUID, - instanceUID: this.request.params.instanceUID - }); - - await bulkDataService.writeBulkData(dicomInstancePathObj); - - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch(e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - } + } diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/series.js b/api/dicom-web/controller/WADO-RS/bulkdata/series.js index 299d6eef..ea2d816b 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/series.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/series.js @@ -1,49 +1,19 @@ -const mongoose = require("mongoose"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); -const dicomSeriesModel = require("../../../../../models/mongodb/models/dicomSeries"); -const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); +const { SeriesBulkDataFactory } = require("./service/bulkdata"); +const { BaseBulkDataController } = require("./base.controller"); +const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); -class SeriesBulkDataController extends Controller { +class SeriesBulkDataController extends BaseBulkDataController { constructor(req, res) { super(req, res); + this.bulkDataFactoryType = SeriesBulkDataFactory; + this.imagePathFactoryType = SeriesImagePathFactory; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ + logAction() { + this.apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ , SeriesInstanceUID: ${this.request.params.seriesUID}`); - - let bulkDataService = new BulkDataService(this.request, this.response); - - try { - let bulkDataArray = await bulkDataService.getSeriesBulkData(); - for (let bulkData of bulkDataArray) { - await bulkDataService.writeBulkData(bulkData); - } - - let dicomInstancePathObjArray = await dicomSeriesModel.getPathGroupOfInstances({ - studyUID: this.request.params.studyUID, - seriesUID: this.request.params.seriesUID - }); - - for (let instancePathObj of dicomInstancePathObjArray) { - await bulkDataService.writeBulkData(instancePathObj); - } - - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch (e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - } + } diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index 71ac34d8..32bf3580 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -11,10 +11,12 @@ class BulkDataService { * * @param {import("express").Request} req * @param {import("express").Response} res + * @param {typeof StudyBulkDataFactory | typeof SeriesBulkDataFactory | typeof InstanceBulkDataFactory | typeof SpecificBulkDataFactory } bulkDataFactory */ - constructor(req, res) { + constructor(req, res, bulkDataFactory) { this.request = req; this.response = res; + this.bulkDataFactory = new bulkDataFactory({...this.request.params}); this.multipartWriter = new MultipartWriter([], req, res); this.multipartWriter.setHeaderMultipartRelatedContentType("application/octet-stream"); } @@ -47,42 +49,36 @@ class BulkDataService { this.multipartWriter.writeBufferData(fileBuffer); } - async getSpecificBulkData() { + async getBulkData() { + return await this.bulkDataFactory.getBulkData(); + } - let { - studyUID, - seriesUID, - instanceUID, - binaryValuePath - } = this.request.params; +} - let bulkData = await dicomBulkDataModel.findOne({ - $and: [ - { - studyUID - }, - { - seriesUID - }, - { - instanceUID - }, - { - binaryValuePath: { - $regex: `^${binaryValuePath}`, - $options: "gm" - } - } - ] - }).exec(); +class BulkDataFactory { + /** + * + * @param {import("@root/utils/typeDef/dicom").Uids} uids + */ + constructor(uids) { + /** @type {import("@root/utils/typeDef/dicom").Uids} */ + this.uids = uids; + } - return bulkData; + getBulkData() { + throw new Error("Abstract method, not implement"); } +} - async getStudyBulkData() { +class StudyBulkDataFactory extends BulkDataFactory { + constructor(uids) { + super(uids); + } + + async getBulkData() { let { studyUID - } = this.request.params; + } = this.uids; let studyBulkDataArray = await dicomBulkDataModel.find({ $and: [ @@ -94,12 +90,18 @@ class BulkDataService { return studyBulkDataArray; } +} + +class SeriesBulkDataFactory extends BulkDataFactory { + constructor(uids) { + super(uids); + } - async getSeriesBulkData() { + async getBulkData() { let { studyUID, seriesUID - } = this.request.params; + } = this.uids; let seriesBulkDataArray = await dicomBulkDataModel.find({ $and: [ @@ -114,13 +116,20 @@ class BulkDataService { return seriesBulkDataArray; } +} + - async getInstanceBulkData() { +class InstanceBulkDataFactory extends BulkDataFactory { + constructor(uids) { + super(uids); + } + + async getBulkData() { let { studyUID, seriesUID, instanceUID - } = this.request.params; + } = this.uids; let instanceBulkDataArray = await dicomBulkDataModel.find({ $and: [ @@ -140,5 +149,46 @@ class BulkDataService { } } +class SpecificBulkDataFactory extends BulkDataFactory { + constructor(uids) { + super(uids); + } + + async getBulkData() { + + let { + studyUID, + seriesUID, + instanceUID, + binaryValuePath + } = this.uids; + + let bulkData = await dicomBulkDataModel.findOne({ + $and: [ + { + studyUID + }, + { + seriesUID + }, + { + instanceUID + }, + { + binaryValuePath: { + $regex: `^${binaryValuePath}`, + $options: "m" + } + } + ] + }).exec(); + + return bulkData; + } +} -module.exports.BulkDataService = BulkDataService; \ No newline at end of file +module.exports.BulkDataService = BulkDataService; +module.exports.StudyBulkDataFactory = StudyBulkDataFactory; +module.exports.SeriesBulkDataFactory = SeriesBulkDataFactory; +module.exports.InstanceBulkDataFactory = InstanceBulkDataFactory; +module.exports.SpecificBulkDataFactory = SpecificBulkDataFactory; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/study.js b/api/dicom-web/controller/WADO-RS/bulkdata/study.js index 0b6dc452..9b499e59 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/study.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/study.js @@ -1,47 +1,18 @@ -const mongoose = require("mongoose"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); -const dicomStudyModel = require("../../../../../models/mongodb/models/dicomStudy"); -const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); +const { StudyBulkDataFactory } = require("./service/bulkdata"); +const { BaseBulkDataController } = require("./base.controller"); +const { StudyImagePathFactory } = require("../service/WADO-RS.service"); -class StudyBulkDataController extends Controller { +class StudyBulkDataController extends BaseBulkDataController { constructor(req, res) { super(req, res); + this.bulkDataFactoryType = StudyBulkDataFactory; + this.imagePathFactoryType = StudyImagePathFactory; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}`); - - let bulkDataService = new BulkDataService(this.request, this.response); - - try { - let bulkDataArray = await bulkDataService.getStudyBulkData(); - for (let bulkData of bulkDataArray) { - await bulkDataService.writeBulkData(bulkData); - } - - let dicomInstancePathObjArray = await dicomStudyModel.getPathGroupOfInstances({ - studyUID: this.request.params.studyUID - }); - - for(let instancePathObj of dicomInstancePathObjArray) { - await bulkDataService.writeBulkData(instancePathObj); - } - - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch(e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - + logAction() { + this.apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}`); } + } diff --git a/error/controller.handler.js b/error/controller.handler.js index 145c8f95..364444eb 100644 --- a/error/controller.handler.js +++ b/error/controller.handler.js @@ -11,11 +11,13 @@ class ControllerErrorHandler { static raiseInternalServerError(e, apiLogger, response) { apiLogger.logger.error(e); - response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - - return response.json(getInternalServerErrorMessage("An exception occurred")); + if (!response.headersSent) { + response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return response.json(getInternalServerErrorMessage("An exception occurred")); + } + return response.end(); } } From a9dfc8f9027fc5ccaec710b869253cd01ee8c6b0 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 21:29:04 +0800 Subject: [PATCH 097/365] refactor: query dicom json factory --- .../QIDO-RS/service/QIDO-RS.service.js | 306 +----------------- .../service/query-dicom-json-factory.js | 210 ++++++++++++ dimse/queryBuilder.js | 2 +- test/QIDO-RS-Service/common.test.js | 5 +- test/QIDO-RS-Service/patient.test.js | 45 ++- test/delete.test.js | 20 +- test/query.test.js | 66 ++-- test/store-instances.test.js | 1 - 8 files changed, 291 insertions(+), 364 deletions(-) create mode 100644 api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 761d202e..cc470810 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -1,17 +1,6 @@ -const urlObj = require("url"); -const mongoose = require("mongoose"); const _ = require("lodash"); -const { mongoDateQuery, timeQuery } = require("../../../../../models/mongodb/service"); const { dictionary } = require("../../../../../models/DICOM/dicom-tags-dic"); -const { - tagsOfRequiredMatching -} = require("../../../../../models/DICOM/dicom-tags-mapping"); -const { logger } = require("../../../../../utils/logs/log"); -const { raccoonConfig } = require("../../../../../config-class"); const { DicomWebService } = require("../../../service/dicom-web.service"); -const dicomWebApiPath = raccoonConfig.dicomWebConfig.apiPath; -const dicomModel = require("../../../../../models/mongodb/models/dicom"); -const patientModel = require("../../../../../models/mongodb/models/patient"); const { DicomWebServiceError, DicomWebStatusCodes @@ -19,13 +8,22 @@ const { const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); +const { QueryPatientDicomJsonFactory, QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("./query-dicom-json-factory"); + +const HierarchyQueryDicomJsonFactory = Object.freeze({ + patient: QueryPatientDicomJsonFactory, + study: QueryStudyDicomJsonFactory, + series: QuerySeriesDicomJsonFactory, + instance: QueryInstanceDicomJsonFactory +}); class QidoRsService { /** * * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {import('express').Response} res + * @param { "patient" | "study" | "series" | "instance" } level */ constructor(req, res, level="instance") { this.request = req; @@ -98,9 +96,9 @@ class QidoRsService { JSON.stringify({...queryOptions.requestParams,...queryOptions.query}), "UTF-8" ); - let qidoDicomJsonFactory = new QidoDicomJsonFactory(queryOptions, this.level); + let dicomJsonFactory = new HierarchyQueryDicomJsonFactory[this.level](queryOptions); - let dicomJson = await qidoDicomJsonFactory.getDicomJson(); + let dicomJson = await dicomJsonFactory.getDicomJson(); let dicomJsonLength = _.get(dicomJson, "length", 0); if (dicomJsonLength > 0) { @@ -136,37 +134,6 @@ class QidoRsService { } -class QidoDicomJsonFactory { - - /** - * - * @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @param {string} level - */ - constructor(queryOptions, level="instance") { - this.level = level; - - this.getDicomJsonByLevel = { - "patient": async() => { - return await getPatientDicomJson(queryOptions); - }, - "study": async () => { - return await getStudyDicomJson(queryOptions); - }, - "series": async () => { - return await getSeriesDicomJson(queryOptions); - }, - "instance": async () => { - return await getInstanceDicomJson(queryOptions); - } - }; - } - - async getDicomJson() { - return await this.getDicomJsonByLevel[this.level](); - } -} - /** * Convert All of name(tags, keyword) of queries to tags number * @param {Object} iParam The request query. @@ -206,254 +173,5 @@ function convertAllQueryToDICOMTag(iParam) { } //#endregion -function checkIsOr(value, keyName) { - if (_.isObject(value) && _.get(value[keyName], "$or")) { - return true; - } - return false; -} - -/** - * convert value that contains comma to $or query of MongoDB - * @param {string} iKey - * @param {string} iValue - */ -function commaValue(iKey, iValue) { - let $or = []; - iValue = iValue.split(","); - for (let i = 0; i < iValue.length; i++) { - let obj = {}; - obj[iKey] = iValue[i]; - $or.push(obj); - } - return $or; -} - -/** - * - * @param {string} value - * @returns - */ -function getWildCardQuery(value) { - let wildCardIndex = value.indexOf("*"); - let questionIndex = value.indexOf("?"); - - if (wildCardIndex >= 0 || questionIndex >= 0) { - value = value.replace(/\*/gm, ".*"); - value = value.replace(/\?/gm, "."); - value = value.replace(/\^/gm, "\\^"); - value = "^" + value; - return new RegExp(value, "gm"); - } - - return value; -} - -/** - * convert all request query object to to $or query and push to $and query - * @param {Object} iQuery - * @returns - */ -async function convertRequestQueryToMongoQuery(iQuery) { - let queryKey = Object.keys(iQuery); - let mongoQs = { - $match: { - $and: [] - } - }; - for (let i = 0; i < queryKey.length; i++) { - let mongoOrs = { - $or: [] - }; - let nowKey = queryKey[i]; - let value = commaValue(nowKey, iQuery[nowKey]); - for (let x = 0; x < value.length; x++) { - let nowValue = value[x][nowKey]; - value[x][nowKey] = getWildCardQuery(nowValue); - - try { - let keySplit = nowKey.split("."); - let tag = keySplit[keySplit.length - 2]; - let vrOfTag = dictionary.tagVR[tag]; - await vrQueryLookup[vrOfTag.vr](value[x], nowKey); - } catch (e) { - if (!(e instanceof TypeError)) console.error(e); - } - - if (checkIsOr(value[x], nowKey)) { - mongoOrs.$or.push(..._.get(value[x][nowKey], "$or")); - } else { - mongoOrs.$or.push(value[x]); - } - } - mongoQs.$match.$and.push(mongoOrs); - } - return mongoQs.$match.$and.length == 0 - ? { - $match: {} - } - : mongoQs; -} - -/** - * - * @param {any[]} arr - * @returns - */ -function sortArrayObjByFieldKey(arr) { - return arr.map(v => sortObjByFieldKey(v)); -} - -function sortObjByFieldKey(obj) { - return _(obj).toPairs().sortBy(0).fromPairs().value(); -} - -const vrQueryLookup = { - DA: async (value, tag) => { - let q = await mongoDateQuery(value, tag, false); - }, - DT: async (value, tag) => { - let q = await mongoDateQuery(value, tag, false, "YYYYMMDDhhmmss.SSSSSSZZ"); - }, - PN: async (value, tag) => { - let queryValue = _.cloneDeep(value[tag]); - value[tag] = { - $or : [ - { - [`${tag}.Alphabetic`] : queryValue - }, - { - [`${tag}.familyName`] : queryValue - }, - { - [`${tag}.givenName`] : queryValue - } , - { - [`${tag}.middleName`] : queryValue - } , - { - [`${tag}.prefix`] : queryValue - }, - { - [`${tag}.suffix`] : queryValue - } - ]}; - }, - TM: async (value, tag) => { - value[tag] = timeQuery(value, tag); - } -}; - -/** - * - * @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -async function getPatientDicomJson(queryOptions) { - try { - let mongoQuery = await convertRequestQueryToMongoQuery(queryOptions.query); - - let query = { - ...queryOptions.requestParams, - ...mongoQuery.$match - }; - logger.info(`[QIDO-RS] [Query for MongoDB: ${JSON.stringify(query)}]`); - - queryOptions.query = { ...query }; - let docs = await patientModel.getDicomJson(queryOptions); - - let sortedInstanceDicomJson = sortArrayObjByFieldKey(docs); - - return sortedInstanceDicomJson; - } catch (e) { - console.error("get Series DICOM error", e); - throw e; - } -} - -/** - * - * @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -async function getStudyDicomJson(queryOptions) { - logger.info(`[QIDO-RS] [Query Study Level]`); - - try { - let query = await convertRequestQueryToMongoQuery(queryOptions.query); - queryOptions.query = { - ...queryOptions.requestParams, - ...query.$match - }; - - logger.info(`[QIDO-RS] [Query for MongoDB: ${JSON.stringify(queryOptions.query)}]`); - - let docs = await mongoose.model("dicomStudy").getDicomJson(queryOptions); - - let sortedTagsStudyDicomJson = sortArrayObjByFieldKey(docs); - - return sortedTagsStudyDicomJson; - } catch (e) { - console.error("get Study DICOM error", e); - throw e; - } -} - -/** - * - * @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -async function getSeriesDicomJson(queryOptions) { - try { - let mongoQuery = await convertRequestQueryToMongoQuery(queryOptions.query); - - let query = { - ...queryOptions.requestParams, - ...mongoQuery.$match - }; - logger.info(`[QIDO-RS] [Query for MongoDB: ${JSON.stringify(query)}]`); - - queryOptions.query = { ...query }; - let docs = await mongoose.model("dicomSeries").getDicomJson(queryOptions); - - let sortedTagsSeriesDicomJson = sortArrayObjByFieldKey(docs); - - return sortedTagsSeriesDicomJson; - } catch (e) { - console.error("get Series DICOM error", e); - throw e; - } -} - -/** - * - * @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ -async function getInstanceDicomJson(queryOptions) { - try { - let mongoQuery = await convertRequestQueryToMongoQuery(queryOptions.query); - - let query = { - ...queryOptions.requestParams, - ...mongoQuery.$match - }; - logger.info(`[QIDO-RS] [Query for MongoDB: ${JSON.stringify(query)}]`); - - queryOptions.query = { ...query }; - let docs = await dicomModel.getDicomJson(queryOptions); - - let sortedInstanceDicomJson = sortArrayObjByFieldKey(docs); - - return sortedInstanceDicomJson; - } catch (e) { - console.error("get Series DICOM error", e); - throw e; - } -} - module.exports.QidoRsService = QidoRsService; -module.exports.QidoDicomJsonFactory = QidoDicomJsonFactory; module.exports.convertAllQueryToDICOMTag = convertAllQueryToDICOMTag; -module.exports.convertRequestQueryToMongoQuery = convertRequestQueryToMongoQuery; diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js new file mode 100644 index 00000000..ea1c19ee --- /dev/null +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -0,0 +1,210 @@ +const _ = require("lodash"); +const patientModel = require("@models/mongodb/models/patient"); +const dicomStudyModel = require("@models/mongodb/models/dicomStudy"); +const dicomSeriesModel = require("@models/mongodb/models/dicomSeries"); +const dicomModel = require("@models/mongodb/models/dicom"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { mongoDateQuery, timeQuery } = require("@models/mongodb/service"); + + + +function checkIsOr(value, keyName) { + if (_.isObject(value) && _.get(value[keyName], "$or")) { + return true; + } + return false; +} + +/** + * convert value that contains comma to $or query of MongoDB + * @param {string} iKey + * @param {string} iValue + */ +function commaValue(iKey, iValue) { + let $or = []; + iValue = iValue.split(","); + for (let i = 0; i < iValue.length; i++) { + let obj = {}; + obj[iKey] = iValue[i]; + $or.push(obj); + } + return $or; +} + +/** + * + * @param {string} value + * @returns + */ +function getWildCardQuery(value) { + let wildCardIndex = value.indexOf("*"); + let questionIndex = value.indexOf("?"); + + if (wildCardIndex >= 0 || questionIndex >= 0) { + value = value.replace(/\*/gm, ".*"); + value = value.replace(/\?/gm, "."); + value = value.replace(/\^/gm, "\\^"); + value = "^" + value; + return new RegExp(value, "gm"); + } + + return value; +} + +/** + * convert all request query object to to $or query and push to $and query + * @param {Object} iQuery + * @returns + */ +async function convertRequestQueryToMongoQuery(iQuery) { + let queryKey = Object.keys(iQuery); + let mongoQs = { + $match: { + $and: [] + } + }; + for (let i = 0; i < queryKey.length; i++) { + let mongoOrs = { + $or: [] + }; + let nowKey = queryKey[i]; + let value = commaValue(nowKey, iQuery[nowKey]); + for (let x = 0; x < value.length; x++) { + let nowValue = value[x][nowKey]; + value[x][nowKey] = getWildCardQuery(nowValue); + + try { + let keySplit = nowKey.split("."); + let tag = keySplit[keySplit.length - 2]; + let vrOfTag = dictionary.tagVR[tag]; + await vrQueryLookup[vrOfTag.vr](value[x], nowKey); + } catch (e) { + if (!(e instanceof TypeError)) console.error(e); + } + + if (checkIsOr(value[x], nowKey)) { + mongoOrs.$or.push(..._.get(value[x][nowKey], "$or")); + } else { + mongoOrs.$or.push(value[x]); + } + } + mongoQs.$match.$and.push(mongoOrs); + } + return mongoQs.$match.$and.length == 0 + ? { + $match: {} + } + : mongoQs; +} + +const vrQueryLookup = { + DA: async (value, tag) => { + let q = await mongoDateQuery(value, tag, false); + }, + DT: async (value, tag) => { + let q = await mongoDateQuery(value, tag, false, "YYYYMMDDhhmmss.SSSSSSZZ"); + }, + PN: async (value, tag) => { + let queryValue = _.cloneDeep(value[tag]); + value[tag] = { + $or : [ + { + [`${tag}.Alphabetic`] : queryValue + }, + { + [`${tag}.familyName`] : queryValue + }, + { + [`${tag}.givenName`] : queryValue + } , + { + [`${tag}.middleName`] : queryValue + } , + { + [`${tag}.prefix`] : queryValue + }, + { + [`${tag}.suffix`] : queryValue + } + ]}; + }, + TM: async (value, tag) => { + value[tag] = timeQuery(value, tag); + } +}; + +/** + * + * @param {any[]} arr + * @returns + */ +function sortArrayObjByFieldKey(arr) { + return arr.map(v => sortObjByFieldKey(v)); +} + +function sortObjByFieldKey(obj) { + return _(obj).toPairs().sortBy(0).fromPairs().value(); +} + + +class QueryDicomJsonFactory { + constructor(queryOptions) { + this.queryOptions = queryOptions; + this.model = patientModel; + } + + async getProcessedQueryOptions() { + let mongoQuery = await convertRequestQueryToMongoQuery(this.queryOptions.query); + + let query = { + ...this.queryOptions.requestParams, + ...mongoQuery.$match + }; + + this.queryOptions.query = { ...query }; + return this.queryOptions; + } + + async getDicomJson() { + let processedQueryOptions = await this.getProcessedQueryOptions(); + let docs = await this.model.getDicomJson(processedQueryOptions); + + let sortedTagsSeriesDicomJson = sortArrayObjByFieldKey(docs); + + return sortedTagsSeriesDicomJson; + } +} + +class QueryPatientDicomJsonFactory extends QueryDicomJsonFactory { + constructor(queryOptions) { + super(queryOptions); + this.model = patientModel; + } +} + +class QueryStudyDicomJsonFactory extends QueryDicomJsonFactory { + constructor(queryOptions) { + super(queryOptions); + this.model = dicomStudyModel; + } +} + +class QuerySeriesDicomJsonFactory extends QueryDicomJsonFactory { + constructor(queryOptions) { + super(queryOptions); + this.model = dicomSeriesModel; + } +} + +class QueryInstanceDicomJsonFactory extends QueryDicomJsonFactory { + constructor(queryOptions) { + super(queryOptions); + this.model = dicomModel; + } +} + +module.exports.QueryPatientDicomJsonFactory = QueryPatientDicomJsonFactory; +module.exports.QueryStudyDicomJsonFactory = QueryStudyDicomJsonFactory; +module.exports.QuerySeriesDicomJsonFactory = QuerySeriesDicomJsonFactory; +module.exports.QueryInstanceDicomJsonFactory = QueryInstanceDicomJsonFactory; +module.exports.convertRequestQueryToMongoQuery = convertRequestQueryToMongoQuery; diff --git a/dimse/queryBuilder.js b/dimse/queryBuilder.js index 4c194b85..4aeaa73f 100644 --- a/dimse/queryBuilder.js +++ b/dimse/queryBuilder.js @@ -4,7 +4,7 @@ const { Attributes } = require("@dcm4che/data/Attributes"); const { queryTagsOfEachLevel } = require("./queryTagsOfEachLevel"); const { StringUtils } = require("@dcm4che/util/StringUtils"); const { intTagToString } = require("./utils"); -const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); class DimseQueryBuilder { diff --git a/test/QIDO-RS-Service/common.test.js b/test/QIDO-RS-Service/common.test.js index d9e9d430..5766a711 100644 --- a/test/QIDO-RS-Service/common.test.js +++ b/test/QIDO-RS-Service/common.test.js @@ -4,11 +4,12 @@ const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); const { - convertAllQueryToDICOMTag, - convertRequestQueryToMongoQuery + convertAllQueryToDICOMTag } = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertRequestQueryToMongoQuery } = require("../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const moment = require("moment"); + describe("QIDO-RS Service Common Function", () => { /** diff --git a/test/QIDO-RS-Service/patient.test.js b/test/QIDO-RS-Service/patient.test.js index 897a8ea9..37180fb4 100644 --- a/test/QIDO-RS-Service/patient.test.js +++ b/test/QIDO-RS-Service/patient.test.js @@ -3,20 +3,17 @@ const patientModel = require("../../models/mongodb/models/patient"); const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); -const { - QidoDicomJsonFactory, - convertAllQueryToDICOMTag, - convertRequestQueryToMongoQuery -} = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDICOMTag } = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { QueryPatientDicomJsonFactory } = require("../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); -describe("Patient QIDO-RS Service", async() => { +describe("Patient QIDO-RS Service", async () => { let fakePatientData = { "patientID": "foobar123456", "00100010": { "vr": "PN", "Value": [ { - "Alphabetic" : "John^Doe" + "Alphabetic": "John^Doe" } ] }, @@ -85,7 +82,7 @@ describe("Patient QIDO-RS Service", async() => { } }; - before(async() => { + before(async () => { let cloneFakePatientData = _.cloneDeep(fakePatientData); _.set(cloneFakePatientData, "studyPath", "/foo/bar"); let dicomJsonModel = new DicomJsonModel(cloneFakePatientData); @@ -95,21 +92,21 @@ describe("Patient QIDO-RS Service", async() => { await dicomJsonModel.storePatientCollection(cloneFakePatientData); }); - describe("Query `PatientID (0010, 0020)` using `QidoDicomJsonFactory`", () => { + describe("Query `PatientID (0010, 0020)` using `QueryPatientDicomJsonFactory`", () => { it("Should search PatientID=`foobar123456` patient and return 1", async () => { let q = { "00100020": "foobar123456" }; q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { ...q }, limit: 10, skip: 0 - }, "patient"); - let patientJson = await qidoDicomJsonFactory.getDicomJson(); + }); + let patientJson = await dicomJsonFactory.getDicomJson(); expect(patientJson).is.an("array").length(1); }); @@ -119,34 +116,34 @@ describe("Patient QIDO-RS Service", async() => { }; q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { ...q }, limit: 10, skip: 0 - }, "patient"); - let patientJson = await qidoDicomJsonFactory.getDicomJson(); + }); + let patientJson = await dicomJsonFactory.getDicomJson(); expect(patientJson).is.an("array").length(0); }); }); - describe("Query `PatientName (0010,0010)` using `QidoDicomJsonFactory`", () => { + describe("Query `PatientName (0010,0010)` using `QueryPatientDicomJsonFactory`", () => { it("Should search PatientName=`John*` patient and return 1", async () => { let q = { "00100010": "John*" }; q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { ...q }, limit: 10, skip: 0 - }, "patient"); - let patientJson = await qidoDicomJsonFactory.getDicomJson(); + }); + let patientJson = await dicomJsonFactory.getDicomJson(); expect(patientJson).is.an("array").length(2); }); @@ -156,21 +153,21 @@ describe("Patient QIDO-RS Service", async() => { }; q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { ...q }, limit: 10, skip: 0 - }, "patient"); - let patientJson = await qidoDicomJsonFactory.getDicomJson(); + }); + let patientJson = await dicomJsonFactory.getDicomJson(); expect(patientJson).is.an("array").length(0); }); }); - - after(async()=> { + + after(async () => { await patientModel.deleteOne({ patientID: "foobar123456" }); diff --git a/test/delete.test.js b/test/delete.test.js index c3383a9a..1ac74bd4 100644 --- a/test/delete.test.js +++ b/test/delete.test.js @@ -1,7 +1,7 @@ const mongoose = require("mongoose"); const { expect } = require("chai"); -const { QidoDicomJsonFactory } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { DeleteService } = require("../api/dicom-web/controller/WADO-RS/deletion/service/delete"); +const { QueryInstanceDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryStudyDicomJsonFactory } = require("../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const studyInstanceUid = "1.3.6.1.4.1.14519.5.2.1.7085.2626.192997540292073877946622133586"; @@ -46,7 +46,7 @@ describe("Delete DICOM Instances by SOPInstanceUID", async () => { it("Should delete instance and expect 4 instances in series", async function () { await deleteBySopInstanceUid(); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryInstanceDicomJsonFactory({ query: {}, requestParams: { studyUID: studyInstanceUid, @@ -54,9 +54,9 @@ describe("Delete DICOM Instances by SOPInstanceUID", async () => { }, limit: 100, skip: 0 - }, "instance"); + }); - let dicomJson = await qidoDicomJsonFactory.getDicomJson(); + let dicomJson = await dicomJsonFactory.getDicomJson(); expect(dicomJson).have.lengthOf(4); }); @@ -67,16 +67,16 @@ describe("Delete DICOM Instances by SeriesInstanceUID", async () => { it("Should delete series and expect 2 series in study", async function (){ await deleteBySeriesInstanceUid(); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QuerySeriesDicomJsonFactory({ query: {}, requestParams: { studyUID: studyInstanceUid }, limit: 100, skip: 0 - }, "series"); + }); - let dicomJson = await qidoDicomJsonFactory.getDicomJson(); + let dicomJson = await dicomJsonFactory.getDicomJson(); expect(dicomJson).have.lengthOf(2); }); @@ -87,14 +87,14 @@ describe("Delete DICOM Instances by StudyInstanceUID", async () => { it("Should delete study and expect 3 studies", async () => { await deleteByStudyInstanceUid(); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: {}, requestParams: {}, limit: 100, skip: 0 - }, "study"); + }); - let dicomJson = await qidoDicomJsonFactory.getDicomJson(); + let dicomJson = await dicomJsonFactory.getDicomJson(); expect(dicomJson).have.lengthOf(3); }); diff --git a/test/query.test.js b/test/query.test.js index 40a44a39..f38a4a2e 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -9,7 +9,9 @@ const dicomStudyModel = require("../models/mongodb/models/dicomStudy"); const dicomSeriesModel = require("../models/mongodb/models/dicomSeries"); const dicomModel = require("../models/mongodb/models/dicom"); const { expect } = require("chai"); -const { QidoDicomJsonFactory, convertAllQueryToDICOMTag } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDICOMTag } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); + describe("Query DICOM of study, series, and instance level", async () => { @@ -26,15 +28,15 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, limit: 100, skip: 0 - }, "study"); + }); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); expect(studyDicomJson).is.an("array").have.lengthOf(2); }); @@ -45,15 +47,15 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, limit: 100, skip: 0 - }, "study"); + }); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); expect(studyDicomJson).is.an("array").have.lengthOf(0); }); }); @@ -66,7 +68,7 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, @@ -74,7 +76,7 @@ describe("Query DICOM of study, series, and instance level", async () => { skip: 0 }, "study"); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); studyForSecondSeriesTest = studyDicomJson[0]; expect(studyDicomJson).is.an("array").have.lengthOf(1); }); @@ -89,7 +91,7 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, @@ -97,7 +99,7 @@ describe("Query DICOM of study, series, and instance level", async () => { skip: 0 }, "study"); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); expect(studyDicomJson).is.an("array").have.lengthOf(0); }); @@ -109,7 +111,7 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, @@ -117,7 +119,7 @@ describe("Query DICOM of study, series, and instance level", async () => { skip: 0 }, "study"); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); expect(studyDicomJson).is.an("array").have.lengthOf(1); }); }); @@ -131,15 +133,15 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, limit: 100, skip: 0 - }, "study"); + }); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); expect(studyDicomJson).is.an("array").have.lengthOf(0); }); @@ -151,15 +153,15 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, limit: 100, skip: 0 - }, "study"); + }); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); expect(studyDicomJson).is.an("array").have.lengthOf(1); }); }); @@ -173,7 +175,7 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, @@ -181,7 +183,7 @@ describe("Query DICOM of study, series, and instance level", async () => { skip: 0 }, "study"); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); expect(studyDicomJson).is.an("array").have.lengthOf(0); }); @@ -193,15 +195,15 @@ describe("Query DICOM of study, series, and instance level", async () => { q = convertAllQueryToDICOMTag(q); - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { ...q }, limit: 100, skip: 0 - }, "study"); + }); - let studyDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let studyDicomJson = await dicomJsonFactory.getDicomJson(); studyForFirstSeriesTest = studyDicomJson[0]; expect(studyDicomJson).is.an("array").have.lengthOf(1); }); @@ -211,7 +213,7 @@ describe("Query DICOM of study, series, and instance level", async () => { describe("Search For Series", () => { it("(step 150): Should use StudyInstanceUID search series and expect 3 series", async () => { - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QuerySeriesDicomJsonFactory({ query: {}, requestParams: { studyUID: studyForFirstSeriesTest["0020000D"]["Value"][0] @@ -220,23 +222,23 @@ describe("Query DICOM of study, series, and instance level", async () => { skip: 0 }, "series"); - let seriesDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let seriesDicomJson = await dicomJsonFactory.getDicomJson(); seriesForFirstInstanceTest = seriesDicomJson; expect(seriesDicomJson).is.an("array").have.lengthOf(3); }); it("Should use StudyInstanceUID from step 170 search series and expect 3 series", async () => { - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QuerySeriesDicomJsonFactory({ query: {}, requestParams: { studyUID: studyForSecondSeriesTest["0020000D"]["Value"][0] }, limit: 100, skip: 0 - }, "series"); + }); - let seriesDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let seriesDicomJson = await dicomJsonFactory.getDicomJson(); expect(seriesDicomJson).is.an("array").have.lengthOf(3); }); }); @@ -245,7 +247,7 @@ describe("Query DICOM of study, series, and instance level", async () => { it("Should expect 1 image, 5 images, and 5 images in 3 series respectively from step 150's series", async () => { let seriesImageCount = []; for (let dicomJson of seriesForFirstInstanceTest) { - let qidoDicomJsonFactory = new QidoDicomJsonFactory({ + let dicomJsonFactory = new QueryInstanceDicomJsonFactory({ query: {}, requestParams: { studyUID: dicomJson["0020000D"]["Value"][0], @@ -253,9 +255,9 @@ describe("Query DICOM of study, series, and instance level", async () => { }, limit: 100, skip: 0 - }, "instance"); + }); - let instanceDicomJson = await qidoDicomJsonFactory.getDicomJson(); + let instanceDicomJson = await dicomJsonFactory.getDicomJson(); seriesImageCount.push(instanceDicomJson.length); } seriesImageCount.sort((a, b) => a - b); diff --git a/test/store-instances.test.js b/test/store-instances.test.js index 24236276..809799cd 100644 --- a/test/store-instances.test.js +++ b/test/store-instances.test.js @@ -9,7 +9,6 @@ const dicomStudyModel = require("../models/mongodb/models/dicomStudy"); const dicomSeriesModel = require("../models/mongodb/models/dicomSeries"); const dicomModel = require("../models/mongodb/models/dicom"); const { expect } = require("chai"); -const { QidoDicomJsonFactory, convertAllQueryToDICOMTag } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); async function storeDicomInstancesAndGet4Patients() { let stowRsService = new StowRsService({ From d13c5a0b7d29bfbcd78a99707bd6d6b740c297b7 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 22:02:24 +0800 Subject: [PATCH 098/365] refactor: duplicate codes in qido controllers --- .../controller/QIDO-RS/allPatient.js | 33 +++-------------- .../controller/QIDO-RS/base.controller.js | 30 ++++++++++++++++ .../controller/QIDO-RS/queryAllInstances.js | 28 +++------------ .../controller/QIDO-RS/queryAllSeries.js | 35 +++---------------- .../controller/QIDO-RS/queryAllStudies.js | 31 +++------------- .../QIDO-RS/queryStudies-Instances.js | 29 +++------------ .../QIDO-RS/queryStudies-Series-Instance.js | 30 +++------------- .../controller/QIDO-RS/queryStudies-Series.js | 30 +++------------- 8 files changed, 65 insertions(+), 181 deletions(-) create mode 100644 api/dicom-web/controller/QIDO-RS/base.controller.js diff --git a/api/dicom-web/controller/QIDO-RS/allPatient.js b/api/dicom-web/controller/QIDO-RS/allPatient.js index cec7a0c0..852f22f4 100644 --- a/api/dicom-web/controller/QIDO-RS/allPatient.js +++ b/api/dicom-web/controller/QIDO-RS/allPatient.js @@ -1,36 +1,13 @@ -const { - QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); +const { BaseQueryController } = require("./base.controller"); -class QueryAllPatientsController extends Controller { +class QueryAllPatientsController extends BaseQueryController { constructor(req, res) { super(req, res); + this.level = "patient"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info("Query all patients"); - - try { - let qidoRsService = new QidoRsService(this.request, this.response, "patient"); - - await qidoRsService.getAndResponseDicomJson(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } + logAction() { + this.apiLogger.logger.info("Query all patients"); } } diff --git a/api/dicom-web/controller/QIDO-RS/base.controller.js b/api/dicom-web/controller/QIDO-RS/base.controller.js new file mode 100644 index 00000000..d9a2f162 --- /dev/null +++ b/api/dicom-web/controller/QIDO-RS/base.controller.js @@ -0,0 +1,30 @@ +const { ControllerErrorHandler } = require("@error/controller.handler"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { QidoRsService } = require("./service/QIDO-RS.service"); + +class BaseQueryController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "QIDO-RS"); + this.apiLogger.addTokenValue(); + this.level = "patient"; + } + + logAction() { + throw new Error("Not Implemented"); + } + + async mainProcess() { + this.logAction(); + + try { + let qidoRsService = new QidoRsService(this.request, this.response, this.level); + await qidoRsService.getAndResponseDicomJson(); + } catch (e) { + return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + } + } +} + +module.exports.BaseQueryController = BaseQueryController; \ No newline at end of file diff --git a/api/dicom-web/controller/QIDO-RS/queryAllInstances.js b/api/dicom-web/controller/QIDO-RS/queryAllInstances.js index 7b09c869..5378306e 100644 --- a/api/dicom-web/controller/QIDO-RS/queryAllInstances.js +++ b/api/dicom-web/controller/QIDO-RS/queryAllInstances.js @@ -3,34 +3,16 @@ const { } = require("./service/QIDO-RS.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); +const { BaseQueryController } = require("./base.controller"); -class QueryAllInstancesController extends Controller { +class QueryAllInstancesController extends BaseQueryController { constructor(req, res) { super(req, res); + this.level = "instance"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info("Query all instances"); - - try { - let qidoRsService = new QidoRsService(this.request, this.response, "instance"); - - await qidoRsService.getAndResponseDicomJson(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } + logAction() { + this.apiLogger.logger.info("Query all instances"); } } diff --git a/api/dicom-web/controller/QIDO-RS/queryAllSeries.js b/api/dicom-web/controller/QIDO-RS/queryAllSeries.js index fef3e682..32da7961 100644 --- a/api/dicom-web/controller/QIDO-RS/queryAllSeries.js +++ b/api/dicom-web/controller/QIDO-RS/queryAllSeries.js @@ -1,38 +1,13 @@ -const { - QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); +const { BaseQueryController } = require("./base.controller"); -class QueryAllSeriesController extends Controller { +class QueryAllSeriesController extends BaseQueryController { constructor(req, res) { super(req, res); + this.level = "series"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info("Query all series"); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "series"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } + logAction() { + this.apiLogger.logger.info("Query all series"); } } /** diff --git a/api/dicom-web/controller/QIDO-RS/queryAllStudies.js b/api/dicom-web/controller/QIDO-RS/queryAllStudies.js index 78eed23c..47da4ad4 100644 --- a/api/dicom-web/controller/QIDO-RS/queryAllStudies.js +++ b/api/dicom-web/controller/QIDO-RS/queryAllStudies.js @@ -2,37 +2,16 @@ const { QidoRsService } = require("./service/QIDO-RS.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); +const { BaseQueryController } = require("./base.controller"); -class QueryAllStudiesController extends Controller { +class QueryAllStudiesController extends BaseQueryController { constructor(req, res) { super(req, res); + this.level = "study"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query All Studies`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "study"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } + logAction() { + this.apiLogger.logger.info(`Query All Studies`); } } diff --git a/api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js b/api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js index d9d427e7..57bb6c9e 100644 --- a/api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js +++ b/api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js @@ -3,35 +3,16 @@ const { } = require("./service/QIDO-RS.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); +const { BaseQueryController } = require("./base.controller"); -class QueryInstancesOfStudiesController extends Controller { +class QueryInstancesOfStudiesController extends BaseQueryController { constructor(req, res) { super(req, res); + this.level = "instance"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query instances in study, Study UID: ${this.request.params.studyUID}`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "instance"); - - await qidoRsService.getAndResponseDicomJson(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } + logAction() { + this.apiLogger.logger.info(`Query instances in study, Study UID: ${this.request.params.studyUID}`); } } diff --git a/api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js b/api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js index 1044f701..6e145aa5 100644 --- a/api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js +++ b/api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js @@ -4,36 +4,16 @@ const { } = require("./service/QIDO-RS.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); +const { BaseQueryController } = require("./base.controller"); -class QueryInstancesOfSeriesOfStudiesController extends Controller { +class QueryInstancesOfSeriesOfStudiesController extends BaseQueryController { constructor(req, res) { super(req, res); + this.level = "instance"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query instance Level, Study UID: ${this.request.params.studyUID}, Series UID: ${this.request.params.seriesUID}`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "instance"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } + logAction() { + this.apiLogger.logger.info(`Query instance Level, Study UID: ${this.request.params.studyUID}, Series UID: ${this.request.params.seriesUID}`); } } diff --git a/api/dicom-web/controller/QIDO-RS/queryStudies-Series.js b/api/dicom-web/controller/QIDO-RS/queryStudies-Series.js index b375a7a8..b01f936e 100644 --- a/api/dicom-web/controller/QIDO-RS/queryStudies-Series.js +++ b/api/dicom-web/controller/QIDO-RS/queryStudies-Series.js @@ -3,36 +3,16 @@ const { } = require("./service/QIDO-RS.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); +const { BaseQueryController } = require("./base.controller"); -class QuerySeriesOfStudiesController extends Controller { +class QuerySeriesOfStudiesController extends BaseQueryController { constructor(req, res) { super(req, res); + this.level = "series"; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query series Level, Study UID: ${this.request.params.studyUID}`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "series"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } + logAction() { + this.apiLogger.logger.info(`Query series Level, Study UID: ${this.request.params.studyUID}`); } } /** From a9676d82a0a3dc209f5ddc70af7100c9bfbb3e70 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 4 Nov 2023 23:44:40 +0800 Subject: [PATCH 099/365] refactor: duplicate codes in retrieve controllers --- .../controller/WADO-RS/base.controller.js | 152 ++++++++++++++++++ .../controller/WADO-RS/retrieveInstance.js | 82 +--------- .../WADO-RS/retrieveStudy-Series-Instances.js | 78 +-------- .../WADO-RS/retrieveStudyInstances.js | 78 +-------- 4 files changed, 171 insertions(+), 219 deletions(-) create mode 100644 api/dicom-web/controller/WADO-RS/base.controller.js diff --git a/api/dicom-web/controller/WADO-RS/base.controller.js b/api/dicom-web/controller/WADO-RS/base.controller.js new file mode 100644 index 00000000..21658f46 --- /dev/null +++ b/api/dicom-web/controller/WADO-RS/base.controller.js @@ -0,0 +1,152 @@ +const { Controller } = require("@root/api/controller.class"); +const { RetrieveAuditService } = require("./service/retrieveAudit.service"); +const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); +const { WADOZip } = require("./service/WADOZip"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { sendNotSupportedMediaType, getAcceptType, supportInstanceMultipartType, ImageMultipartWriter, InstanceImagePathFactory, multipartContentTypeWriter, StudyImagePathFactory, SeriesImagePathFactory } = require("./service/WADO-RS.service"); +const { ControllerErrorHandler } = require("@error/controller.handler"); + +class BaseRetrieveController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "WADO-RS"); + this.apiLogger.addTokenValue(); + this.zipResponseHandlerType = BaseZipResponseHandler; + this.multipartResponseHandlerType = BaseMultipartRelatedResponseHandler; + } + + logAction() { + throw new Error("Not implemented."); + } + + async mainProcess() { + try { + if (this.request.headers.accept.toLowerCase() === "application/zip") { + return await this.responseZip(); + } else if (this.request.headers.accept.includes("multipart/related")) { + return await this.responseMultipartRelated(); + } else if (this.request.headers.accept.includes("*")) { + this.request.headers.accept = "multipart/related; type=\"application/dicom\""; + return await this.responseMultipartRelated(); + } + + return sendNotSupportedMediaType(this.response, this.request.headers.accept); + } catch (e) { + return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + } + } + + async responseZip() { + let zipResponseHandler = new this.zipResponseHandlerType(this.request, this.response); + await zipResponseHandler.doResponse(); + } + + async responseMultipartRelated() { + let multipartResponseHandler = new this.multipartResponseHandlerType(this.request, this.response); + await multipartResponseHandler.doResponse(); + } +} + + +class BaseZipResponseHandler { + constructor(req, res) { + this.request = req; + this.response = res; + this.wadoZip = new WADOZip(this.request, this.response); + this.zipGetter = this.wadoZip.getZipOfInstanceDICOMFile.bind(this.wadoZip); + } + + async doResponse() { + let retrieveAuditService = new RetrieveAuditService(this.request, this.request.params.studyUID, EventOutcomeIndicator.Success); + + await retrieveAuditService.onBeginRetrieve(); + + let zipResult = await this.zipGetter(); + if (zipResult.status) { + await retrieveAuditService.completedRetrieve(); + return this.response.end(); + } else { + retrieveAuditService.eventResult = EventOutcomeIndicator.MajorFailure; + await retrieveAuditService.completedRetrieve(); + this.response.writeHead(zipResult.code, { + "Content-Type": "application/dicom+json" + }); + return this.response.end(JSON.stringify(zipResult)); + } + } +} + +class StudyZipResponseHandler extends BaseZipResponseHandler { + constructor(req, res) { + super(req, res); + this.zipGetter = this.wadoZip.getZipOfStudyDICOMFiles.bind(this.wadoZip); + } +} + +class SeriesZipResponseHandler extends BaseZipResponseHandler { + constructor(req, res) { + super(req, res); + this.zipGetter = this.wadoZip.getZipOfSeriesDICOMFiles.bind(this.wadoZip); + } +} + +class InstanceZipResponseHandler extends BaseZipResponseHandler { + constructor(req, res) { + super(req, res); + this.zipGetter = this.wadoZip.getZipOfInstanceDICOMFile.bind(this.wadoZip); + } +} + +class BaseMultipartRelatedResponseHandler { + constructor(req, res) { + this.request = req; + this.response = res; + this.imagePathFactoryType = StudyImagePathFactory; + } + + async doResponse() { + let type = getAcceptType(this.request); + let isSupported = supportInstanceMultipartType.indexOf(type) > -1; + if (!isSupported) { + return sendNotSupportedMediaType(this.response, type); + } + + let imageMultipartWriter = new ImageMultipartWriter( + this.request, + this.response, + this.imagePathFactoryType , + multipartContentTypeWriter[type] + ); + + return await imageMultipartWriter.write(); + } +} + +class StudyMultipartRelatedResponseHandler extends BaseMultipartRelatedResponseHandler { + constructor(req, res) { + super(req, res); + this.imagePathFactoryType = StudyImagePathFactory; + } +} + +class SeriesMultipartRelatedResponseHandler extends BaseMultipartRelatedResponseHandler { + constructor(req, res) { + super(req, res); + this.imagePathFactoryType = SeriesImagePathFactory; + } +} + +class InstanceMultipartRelatedResponseHandler extends BaseMultipartRelatedResponseHandler { + constructor(req, res) { + super(req, res); + this.imagePathFactoryType = InstanceImagePathFactory; + } +} + +module.exports.BaseRetrieveController = BaseRetrieveController; +module.exports.StudyZipResponseHandler = StudyZipResponseHandler; +module.exports.SeriesZipResponseHandler = SeriesZipResponseHandler; +module.exports.InstanceZipResponseHandler = InstanceZipResponseHandler; +module.exports.StudyMultipartRelatedResponseHandler = StudyMultipartRelatedResponseHandler; +module.exports.SeriesMultipartRelatedResponseHandler = SeriesMultipartRelatedResponseHandler; +module.exports.InstanceMultipartRelatedResponseHandler = InstanceMultipartRelatedResponseHandler; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/retrieveInstance.js b/api/dicom-web/controller/WADO-RS/retrieveInstance.js index f9f203b1..6cbfd394 100644 --- a/api/dicom-web/controller/WADO-RS/retrieveInstance.js +++ b/api/dicom-web/controller/WADO-RS/retrieveInstance.js @@ -1,82 +1,14 @@ -const wadoService = require("./service/WADO-RS.service"); -const { WADOZip } = require("./service/WADOZip"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); -const { RetrieveAuditService } = require("./service/retrieveAudit.service"); -const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); -class RetrieveInstanceOfSeriesOfStudiesController extends Controller { +const { BaseRetrieveController, InstanceZipResponseHandler, InstanceMultipartRelatedResponseHandler } = require("./base.controller"); +class RetrieveInstanceOfSeriesOfStudiesController extends BaseRetrieveController { constructor(req, res) { super(req, res); + this.zipResponseHandlerType = InstanceZipResponseHandler; + this.multipartResponseHandlerType = InstanceMultipartRelatedResponseHandler; } - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); - apiLogger.logger.info(`Request Accept: ${this.request.headers.accept}`); - - try { - - if (this.request.headers.accept.toLowerCase() === "application/zip") { - return await this.responseZip(); - } else if (this.request.headers.accept.includes("multipart/related")) { - return await this.responseMultipartRelated(); - } else if (this.request.headers.accept.includes("*")){ - this.request.headers.accept = "multipart/related; type=\"application/dicom\""; - return await this.responseMultipartRelated(); - } - - return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } - } - - async responseZip() { - let retrieveAuditService = new RetrieveAuditService(this.request, this.request.params.studyUID, EventOutcomeIndicator.Success); - - let wadoZip = new WADOZip(this.request, this.response); - await retrieveAuditService.onBeginRetrieve(); - - let zipResult = await wadoZip.getZipOfInstanceDICOMFile(); - if (zipResult.status) { - await retrieveAuditService.completedRetrieve(); - return this.response.end(); - } else { - retrieveAuditService.eventResult = EventOutcomeIndicator.MajorFailure; - await retrieveAuditService.completedRetrieve(); - this.response.writeHead(zipResult.code, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(zipResult)); - } - } - - async responseMultipartRelated() { - let type = wadoService.getAcceptType(this.request); - let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; - if (!isSupported) { - return wadoService.sendNotSupportedMediaType(this.response, type); - } - - let imageMultipartWriter = new wadoService.ImageMultipartWriter( - this.request, - this.response, - wadoService.InstanceImagePathFactory, - wadoService.multipartContentTypeWriter[type] - ); - - return await imageMultipartWriter.write(); + logAction() { + this.apiLogger.logger.info(`Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); + this.apiLogger.logger.info(`Request Accept: ${this.request.headers.accept}`); } } diff --git a/api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js b/api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js index 6a998c04..f038b896 100644 --- a/api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js +++ b/api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js @@ -1,80 +1,14 @@ -const { logger } = require("../../../../utils/logs/log"); -const wadoService = require("./service/WADO-RS.service"); -const { WADOZip } = require("./service/WADOZip"); -const errorResponse = require("../../../../utils/errorResponse/errorResponseMessage"); -const { Controller } = require("../../../controller.class"); -const { RetrieveAuditService } = require("./service/retrieveAudit.service"); -const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); +const { BaseRetrieveController, SeriesZipResponseHandler, SeriesMultipartRelatedResponseHandler } = require("./base.controller"); -class RetrieveInstancesOfSeries extends Controller { +class RetrieveInstancesOfSeries extends BaseRetrieveController { constructor(req, res) { super(req, res); + this.zipResponseHandlerType = SeriesZipResponseHandler; + this.multipartResponseHandlerType = SeriesMultipartRelatedResponseHandler; } - async mainProcess() { - try { - logger.info(`[WADO-RS] [Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}] [Request Accept: ${this.request.headers.accept}]`); - - if (this.request.headers.accept.toLowerCase() === "application/zip") { - return await this.responseZip(); - } else if (this.request.headers.accept.includes("multipart/related")) { - return await this.responseMultipartRelated(); - } else if (this.request.headers.accept.includes("*")){ - this.request.headers.accept = "multipart/related; type=\"application/dicom\""; - return await this.responseMultipartRelated(); - } - - return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - logger.error(`[WADO-RS] [Error: ${errorStr}]`); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } - } - - async responseZip() { - let retrieveAuditService = new RetrieveAuditService(this.request, this.request.params.studyUID, EventOutcomeIndicator.Success); - - let wadoZip = new WADOZip(this.request, this.response); - await retrieveAuditService.onBeginRetrieve(); - - let zipResult = await wadoZip.getZipOfSeriesDICOMFiles(); - if (zipResult.status) { - await retrieveAuditService.completedRetrieve(); - return this.response.end(); - } else { - retrieveAuditService.eventResult = EventOutcomeIndicator.MajorFailure; - await retrieveAuditService.completedRetrieve(); - - this.response.writeHead(zipResult.code, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(zipResult)); - } - } - - async responseMultipartRelated() { - let type = wadoService.getAcceptType(this.request); - let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; - if (!isSupported) { - return wadoService.sendNotSupportedMediaType(this.response, type); - } - - let imageMultipartWriter = new wadoService.ImageMultipartWriter( - this.request, - this.response, - wadoService.SeriesImagePathFactory, - wadoService.multipartContentTypeWriter[type] - ); - - return await imageMultipartWriter.write(); + logAction() { + this.apiLogger.logger.info(`[WADO-RS] [Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}] [Request Accept: ${this.request.headers.accept}]`); } } diff --git a/api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js b/api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js index 9440669f..84c1e677 100644 --- a/api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js +++ b/api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js @@ -1,80 +1,14 @@ -const { logger } = require("../../../../utils/logs/log"); -const wadoService = require("./service/WADO-RS.service"); -const { WADOZip } = require("./service/WADOZip"); -const errorResponse = require("../../../../utils/errorResponse/errorResponseMessage"); -const { Controller } = require("../../../controller.class"); -const { RetrieveAuditService } = require("./service/retrieveAudit.service"); -const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); +const { BaseRetrieveController, StudyZipResponseHandler, StudyMultipartRelatedResponseHandler } = require("./base.controller"); -class RetrieveStudyInstancesController extends Controller { +class RetrieveStudyInstancesController extends BaseRetrieveController { constructor(req, res) { super(req, res); + this.zipResponseHandlerType = StudyZipResponseHandler; + this.multipartResponseHandlerType = StudyMultipartRelatedResponseHandler; } - async mainProcess() { - try { - logger.info(`[WADO-RS] [Get study's instances, study UID: ${this.request.params.studyUID}] [Request Accept: ${this.request.headers.accept}]`); - - if (this.request.headers.accept.toLowerCase() === "application/zip") { - return await this.responseZip(); - } else if (this.request.headers.accept.includes("multipart/related")) { - return await this.responseMultipartRelated(); - } else if (this.request.headers.accept.includes("*")) { - this.request.headers.accept = "multipart/related; type=\"application/dicom\""; - return await this.responseMultipartRelated(); - } - - return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - logger.error(`[WADO-RS] [Error: ${errorStr}]`); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } - } - - async responseZip() { - let retrieveAuditService = new RetrieveAuditService(this.request, this.request.params.studyUID, EventOutcomeIndicator.Success); - - let wadoZip = new WADOZip(this.request, this.response); - await retrieveAuditService.onBeginRetrieve(); - - let zipResult = await wadoZip.getZipOfStudyDICOMFiles(); - if (zipResult.status) { - await retrieveAuditService.completedRetrieve(); - return this.response.end(); - } else { - retrieveAuditService.eventResult = EventOutcomeIndicator.MajorFailure; - await retrieveAuditService.completedRetrieve(); - - this.response.writeHead(zipResult.code, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(zipResult)); - } - } - - async responseMultipartRelated() { - let type = wadoService.getAcceptType(this.request); - let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; - if (!isSupported) { - return wadoService.sendNotSupportedMediaType(this.response, type); - } - - let imageMultipartWriter = new wadoService.ImageMultipartWriter( - this.request, - this.response, - wadoService.StudyImagePathFactory, - wadoService.multipartContentTypeWriter[type] - ); - - return await imageMultipartWriter.write(); + logAction() { + this.apiLogger.logger.info(`[WADO-RS] [Get study's instances, study UID: ${this.request.params.studyUID}] [Request Accept: ${this.request.headers.accept}]`); } } From 2f7c9504a803343e95b147cdbf2d97869dc273f1 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 5 Nov 2023 09:58:20 +0800 Subject: [PATCH 100/365] refactor: duplicate codes in dicom schemas - Add base dicom schema definition for reusable - Add DicomSchemaOptionsFactory - The not implemented function need to implement in each schema --- models/mongodb/deleteSchedule.js | 6 +- models/mongodb/models/dicom.js | 324 ++++++++++---------------- models/mongodb/models/dicomSeries.js | 188 ++++----------- models/mongodb/models/dicomStudy.js | 168 +++---------- models/mongodb/models/patient.js | 147 +++--------- models/mongodb/schema/dicom.schema.js | 277 ++++++++++++++++++++++ 6 files changed, 512 insertions(+), 598 deletions(-) create mode 100644 models/mongodb/schema/dicom.schema.js diff --git a/models/mongodb/deleteSchedule.js b/models/mongodb/deleteSchedule.js index ee6ce2f5..18704fcb 100644 --- a/models/mongodb/deleteSchedule.js +++ b/models/mongodb/deleteSchedule.js @@ -44,7 +44,7 @@ async function deleteExpireStudies() { deletedStudy.delete() ]); - await deletedStudy.deleteStudyFolder(); + await deletedStudy.deleteDicomInstances(); } } } @@ -74,7 +74,7 @@ async function deleteExpireSeries() { aDeletedSeries.delete() ]); - await aDeletedSeries.deleteSeriesFolder(); + await aDeletedSeries.deleteDicomInstances(); } } } @@ -94,8 +94,8 @@ async function deleteExpireInstances() { let diff = now.diff(updateAtDate, "days"); if (diff >= 30) { logger.info("delete expired instance: " + instanceUID); - await deletedInstance.deleteInstance(); await deletedInstance.delete(); + await deletedInstance.deleteDicomInstances(); } } } \ No newline at end of file diff --git a/models/mongodb/models/dicom.js b/models/mongodb/models/dicom.js index af9feed8..8ed118c9 100644 --- a/models/mongodb/models/dicom.js +++ b/models/mongodb/models/dicom.js @@ -12,12 +12,8 @@ const { getStoreDicomFullPath, IncludeFieldsFactory } = require("../service"); const { logger } = require("../../../utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { DicomSchemaOptionsFactory, InstanceDocDicomJsonHandler } = require("../schema/dicom.schema"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} let verifyingObserverSchema = new mongoose.Schema( { @@ -32,112 +28,12 @@ let verifyingObserverSchema = new mongoose.Schema( } ); -/** - * @constructs dicomModelSchema - */ -let dicomModelSchema = new mongoose.Schema( - { - "studyUID": { - type: String, - default: void 0, - index: true, - required: true - }, - "seriesUID": { - type: String, - default: void 0, - index: true, - required: true - }, - "instanceUID": { - type: String, - default: void 0, - index: true, - required: true - }, - "deleteStatus": { - type: Number, - default: 0 - }, - "00080020": new mongoose.Schema(dicomJsonAttributeDASchema, { - _id: false, - id: false, - toObject: { - getters: true - } - }), - "00080030": getVRSchema("TM"), - "00080050": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00080061": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00080090": getVRSchema("PN"), - "00100010": getVRSchema("PN"), - "00100020": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "0020000D": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00200010": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00080060": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "0020000E": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00200011": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00400244": new mongoose.Schema(dicomJsonAttributeDASchema, { - _id: false, - id: false, - toObject: { - getters: true - } - }), - "00400275": dicomJsonAttributeSchema, - "0040A073": { - ...dicomJsonAttributeSchema, - Value: [verifyingObserverSchema] - }, - "00080016": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00080018": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - }, - "00200013": { - ...dicomJsonAttributeSchema, - Value: [mongoose.SchemaTypes.String] - } - }, +let dicomSchemaOptions = _.merge( + DicomSchemaOptionsFactory.get("instance", InstanceDocDicomJsonHandler), { strict: false, - versionKey: false, - toObject: { - getters: true - }, methods: { - async incrementDeleteStatus() { - this.deleteStatus = this.deleteStatus + 1; - await this.save(); - }, - async deleteInstance() { + deleteDicomInstances: async function () { let instancePath = this.instancePath; try { logger.warn("Permanently delete instance: " + instancePath); @@ -151,23 +47,9 @@ let dicomModelSchema = new mongoose.Schema( } catch (e) { console.error(e); } - }, - getAttributes: async function () { - let study = this.toObject(); - delete study._id; - delete study.id; - - let jsonStr = JSON.stringify(study); - return await Common.getAttributesFromJsonString(jsonStr); } }, statics: { - getDimseResultCursor: async (query, keys) => { - return mongoose.model("dicom").find(query, keys).setOptions({ - strictQuery: false - }) - .cursor(); - }, getAuditInstancesInfoFromStudyUID: async (studyUID) => { let instances = await mongoose.model("dicom").find({ studyUID }).exec(); @@ -194,60 +76,9 @@ let dicomModelSchema = new mongoose.Schema( return instanceInfos; }, - /** - * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ - getDicomJson: async function (queryOptions) { - + getDicomJsonProjection: function (queryOptions) { let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); - let instanceFields = includeFieldsFactory.getInstanceLevelFields(); - - try { - - let docs = await mongoose - .model("dicom") - .find({ - ...queryOptions.query, - deleteStatus: { - $eq: 0 - } - }, { - ...instanceFields - }) - .setOptions({ - strictQuery: false - }) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .exec(); - - let instanceDicomJson = docs.map(v => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - obj["00081190"] = { - vr: "UR", - Value: [ - `${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}/instances/${obj["00080018"]["Value"][0]}` - ] - }; - - _.set(obj, dictionary.keyword.RetrieveAETitle, { - ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], - Value: [raccoonConfig.aeTitle] - }); - - return obj; - }); - - return instanceDicomJson; - - } catch (e) { - throw e; - } - + return includeFieldsFactory.getInstanceLevelFields(); }, /** * @@ -257,29 +88,9 @@ let dicomModelSchema = new mongoose.Schema( * @param {string} iParam.instanceUID */ getPathOfInstance: async function (iParam) { - let { studyUID, seriesUID, instanceUID } = iParam; - try { - let query = { - $and: [ - { - studyUID: studyUID - }, - { - seriesUID: seriesUID - }, - { - instanceUID: instanceUID - }, - { - deleteStatus: { - $eq: 0 - } - } - ] - }; - let doc = await mongoose.model("dicom").findOne(query, { + let doc = await mongoose.model("dicom").findOne(mongoose.model("dicom").getPathGroupQuery(iParam), { studyUID: 1, seriesUID: 1, instanceUID: 1, @@ -299,6 +110,27 @@ let dicomModelSchema = new mongoose.Schema( throw e; } }, + getPathGroupQuery: function (iParam) { + let { studyUID, seriesUID, instanceUID } = iParam; + return { + $and: [ + { + studyUID: studyUID + }, + { + seriesUID: seriesUID + }, + { + instanceUID: instanceUID + }, + { + deleteStatus: { + $eq: 0 + } + } + ] + }; + }, /** * * @param {string} studyUID @@ -332,11 +164,107 @@ let dicomModelSchema = new mongoose.Schema( .limit(1) .exec(); } - }, - timestamps: true + } } ); +/** + * @constructs dicomModelSchema + */ +let dicomModelSchema = new mongoose.Schema( + { + "studyUID": { + type: String, + default: void 0, + index: true, + required: true + }, + "seriesUID": { + type: String, + default: void 0, + index: true, + required: true + }, + "instanceUID": { + type: String, + default: void 0, + index: true, + required: true + }, + "deleteStatus": { + type: Number, + default: 0 + }, + "00080020": new mongoose.Schema(dicomJsonAttributeDASchema, { + _id: false, + id: false, + toObject: { + getters: true + } + }), + "00080030": getVRSchema("TM"), + "00080050": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00080061": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00080090": getVRSchema("PN"), + "00100010": getVRSchema("PN"), + "00100020": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "0020000D": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00200010": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00080060": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "0020000E": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00200011": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00400244": new mongoose.Schema(dicomJsonAttributeDASchema, { + _id: false, + id: false, + toObject: { + getters: true + } + }), + "00400275": dicomJsonAttributeSchema, + "0040A073": { + ...dicomJsonAttributeSchema, + Value: [verifyingObserverSchema] + }, + "00080016": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00080018": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + }, + "00200013": { + ...dicomJsonAttributeSchema, + Value: [mongoose.SchemaTypes.String] + } + }, + dicomSchemaOptions +); + dicomModelSchema.index({ "0020000D": 1 }); diff --git a/models/mongodb/models/dicomSeries.js b/models/mongodb/models/dicomSeries.js index 06224f69..6529cf20 100644 --- a/models/mongodb/models/dicomSeries.js +++ b/models/mongodb/models/dicomSeries.js @@ -4,179 +4,73 @@ const mongoose = require("mongoose"); const _ = require("lodash"); const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); -const { getStoreDicomFullPathGroup, IncludeFieldsFactory } = require("../service"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { IncludeFieldsFactory } = require("../service"); const { raccoonConfig } = require("@root/config-class"); const { logger } = require("@root/utils/logs/log"); +const { BaseDicomSchemaDef, DicomSchemaOptionsFactory, SeriesDocDicomJsonHandler } = require("../schema/dicom.schema"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -let dicomSeriesSchema = new mongoose.Schema( - { - studyUID: { - type: String, - default: void 0, - index: true, - required: true - }, - seriesUID: { - type: String, - default: void 0, - index: true, - required: true - }, - seriesPath: { - type: String, - default: void 0 - }, - deleteStatus: { - type: Number, - default: 0 - } - }, +let dicomSeriesSchemaOptions = _.merge( + DicomSchemaOptionsFactory.get("series", SeriesDocDicomJsonHandler), { - strict: true, - versionKey: false, - toObject: { - getters: true - }, methods: { - async incrementDeleteStatus() { - this.deleteStatus = this.deleteStatus + 1; - await this.save(); - }, - async deleteSeriesFolder() { + deleteDicomInstances: async function () { let seriesPath = this.seriesPath; logger.warn("Permanently delete series folder: " + seriesPath); await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, seriesPath), { force: true, recursive: true }); - }, - getAttributes: async function () { - let series = this.toObject(); - delete series._id; - delete series.id; - - let jsonStr = JSON.stringify(series); - return await Common.getAttributesFromJsonString(jsonStr); } }, statics: { - getDimseResultCursor: async function (query, keys) { - return mongoose.model("dicomSeries").find(query, keys).setOptions({ - strictQuery: false - }) - .cursor(); - }, /** * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions * @returns */ - getDicomJson: async function (queryOptions) { + getDicomJsonProjection: function (queryOptions) { let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); - let seriesFields = includeFieldsFactory.getSeriesLevelFields(); - - try { - let docs = await mongoose - .model("dicomSeries") - .find({ - ...queryOptions.query, - deleteStatus: { - $eq: 0 - } - }, { - ...seriesFields - }) - .setOptions({ - strictQuery: false - }) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .exec(); - - - let seriesDicomJson = docs.map((v) => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - obj["00081190"] = { - vr: "UR", - Value: [ - `${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}` - ] - }; - - _.set(obj, dictionary.keyword.RetrieveAETitle, { - ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], - Value: [raccoonConfig.aeTitle] - }); - - return obj; - }); - - return seriesDicomJson; - - } catch (e) { - throw e; - } + return includeFieldsFactory.getSeriesLevelFields(); }, - /** - * - * @param {object} iParam - * @param {string} iParam.studyUID - * @param {string} iParam.seriesUID - */ - getPathGroupOfInstances: async function (iParam) { + getPathGroupQuery: function (iParam) { let { studyUID, seriesUID } = iParam; - try { - let query = [ - { - $match: { - $and: [ - { - seriesUID: seriesUID - }, - { - studyUID: studyUID - } - ] + return { + $match: { + $and: [ + { + seriesUID: seriesUID + }, + { + studyUID: studyUID } - }, - { - $group: { - _id: "$seriesUID", - pathList: { - $addToSet: { - studyUID: "$studyUID", - seriesUID: "$seriesUID", - instanceUID: "$instanceUID", - instancePath: "$instancePath" - } - } - } - } - ]; - let docs = await mongoose.model("dicom").aggregate(query); - let pathGroup = _.get(docs, "0.pathList", []); - - let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); - - return fullPathGroup; - } catch (e) { - throw e; - } + ] + } + }; } - }, - timestamps: true + } } ); +let dicomSeriesSchema = new mongoose.Schema( + { + ...BaseDicomSchemaDef + }, + dicomSeriesSchemaOptions +); + +dicomSeriesSchema.add({ + seriesUID: { + type: String, + default: void 0, + index: true, + required: true + }, + seriesPath: { + type: String, + default: void 0 + } +}); + for (let tag in tagsNeedStore.Study) { let vr = tagsNeedStore.Study[tag].vr; let tagSchema = getVRSchema(vr); diff --git a/models/mongodb/models/dicomStudy.js b/models/mongodb/models/dicomStudy.js index d4788c77..6bc5fcca 100644 --- a/models/mongodb/models/dicomStudy.js +++ b/models/mongodb/models/dicomStudy.js @@ -1,168 +1,64 @@ +const _ = require("lodash"); const fsP = require("fs/promises"); const path = require("path"); +const { raccoonConfig } = require("@root/config-class"); const mongoose = require("mongoose"); -const _ = require("lodash"); const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); -const { - getStoreDicomFullPathGroup, - IncludeFieldsFactory -} = require("../service"); -const { - tagsOfRequiredMatching -} = require("../../DICOM/dicom-tags-mapping"); -const { raccoonConfig } = require("../../../config-class"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { BaseDicomSchemaDef, DicomSchemaOptionsFactory, StudyDocDicomJsonHandler } = require("../schema/dicom.schema"); const { logger } = require("@root/utils/logs/log"); +const { IncludeFieldsFactory } = require("../service"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -let dicomStudySchema = new mongoose.Schema( - { - studyUID: { - type: String, - default: void 0, - index: true, - required: true - }, - studyPath: { - type: String, - default: void 0 - }, - deleteStatus: { - type: Number, - default: 0 - } - }, +let dicomStudySchemaOptions = _.merge( + DicomSchemaOptionsFactory.get("study", StudyDocDicomJsonHandler), { - strict: true, - versionKey: false, - toObject: { - getters: true - }, methods: { - async incrementDeleteStatus() { - this.deleteStatus = this.deleteStatus + 1; - await this.save(); - }, - async deleteStudyFolder() { + deleteDicomInstances: async function () { + let studyPath = this.studyPath; logger.warn("Permanently delete study folder: " + studyPath); await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, studyPath), { force: true, recursive: true }); - }, - getAttributes: async function () { - let study = this.toObject(); - delete study._id; - delete study.id; - - let jsonStr = JSON.stringify(study); - return await Common.getAttributesFromJsonString(jsonStr); } }, statics: { - getDimseResultCursor: async function (query, keys) { - return mongoose.model("dicomStudy").find(query, keys).setOptions({ - strictQuery: false - }) - .cursor(); - }, /** * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions * @returns */ - getDicomJson: async function (queryOptions) { + getDicomJsonProjection: function (queryOptions) { let includeFieldsFactory = new IncludeFieldsFactory(queryOptions.includeFields); - let studyFields = includeFieldsFactory.getStudyLevelFields(); - - try { - let docs = await mongoose.model("dicomStudy").find({ - ...queryOptions.query, - deleteStatus: { - $eq: 0 - } - }, studyFields) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .setOptions({ - strictQuery: false - }) - .exec(); - - let studyDicomJson = docs.map((v) => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - obj["00081190"] = { - vr: "UR", - Value: [`${queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}`] - }; - - _.set(obj, dictionary.keyword.RetrieveAETitle, { - ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], - Value: [raccoonConfig.aeTitle] - }); - - return obj; - }); - - return studyDicomJson; - - } catch (e) { - throw e; - } + return includeFieldsFactory.getStudyLevelFields(); }, - /** - * - * @param {Object} iParam - * @param {string} iParam.studyUID - */ - getPathGroupOfInstances: async function (iParam) { + getPathGroupQuery: function (iParam) { let { studyUID } = iParam; - try { - let query = [ - { - $match: { - studyUID: studyUID - } - }, - { - $group: { - _id: "$studyUID", - pathList: { - $addToSet: { - studyUID: "$studyUID", - seriesUID: "$seriesUID", - instanceUID: "$instanceUID", - instancePath: "$instancePath" - } - } - } - } - ]; - let docs = await mongoose.model("dicom").aggregate(query).exec(); - let pathGroup = _.get(docs, "0.pathList", []); - - let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); - - return fullPathGroup; - - } catch (e) { - throw e; - } + return { + $match: { + studyUID: studyUID + } + }; } - }, - timestamps: true + } } ); +let dicomStudySchema = new mongoose.Schema( + { + ...BaseDicomSchemaDef + }, + dicomStudySchemaOptions +); + +dicomStudySchema.add({ + studyPath: { + type: String, + default: void 0 + } +}); + for (let tag in tagsNeedStore.Study) { let vr = tagsNeedStore.Study[tag].vr; let tagSchema = getVRSchema(vr); diff --git a/models/mongodb/models/patient.js b/models/mongodb/models/patient.js index 4c945b5f..79a4b5f9 100644 --- a/models/mongodb/models/patient.js +++ b/models/mongodb/models/patient.js @@ -1,20 +1,40 @@ -const path = require("path"); const mongoose = require("mongoose"); const _ = require("lodash"); const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); -const { getStoreDicomFullPathGroup } = require("../service"); const { tagsOfRequiredMatching } = require("../../DICOM/dicom-tags-mapping"); const { raccoonConfig } = require("@root/config-class"); +const { DicomSchemaOptionsFactory, PatientDocDicomJsonHandler } = require("../schema/dicom.schema"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - +let patientSchemaOptions = _.merge( + DicomSchemaOptionsFactory.get("patient", PatientDocDicomJsonHandler), + { + statics: { + getPathGroupQuery: function (iParam) { + let { patientID } = iParam; + return { + $match: { + "00100020.Value": patientID + } + }; + }, + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJsonProjection: function (queryOptions) { + let fields = {}; + for (let tag in tagsOfRequiredMatching.Patient) { + fields[tag] = 1; + } + return fields; + } + } + } +); let patientSchema = new mongoose.Schema( { @@ -27,103 +47,13 @@ let patientSchema = new mongoose.Schema( studyPaths: { type: [String], default: void 0 - } - }, - { - strict: true, - versionKey: false, - toObject: { - getters: true }, - methods: { - getAttributes: async function () { - let patient = this.toObject(); - delete patient._id; - delete patient.id; - - let jsonStr = JSON.stringify(patient); - return await Common.getAttributesFromJsonString(jsonStr); - } - }, - statics: { - getDimseResultCursor: async function (query, keys) { - return mongoose.model("patient").find(query, keys).setOptions({ - strictQuery: false - }) - .cursor(); - }, - /** - * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @returns - */ - getDicomJson: async function (queryOptions) { - let patientFields = getPatientLevelFields(); - - try { - let docs = await mongoose.model("patient").find(queryOptions.query, patientFields) - .limit(queryOptions.limit) - .skip(queryOptions.skip) - .setOptions({ - strictQuery: false - }) - .exec(); - - - let patientDicomJson = docs.map((v) => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - return obj; - }); - - return patientDicomJson; - - } catch (e) { - throw e; - } - }, - /** - * - * @param {Object} iParam - * @param {string} iParam.studyUID - */ - getPathGroupOfInstances: async function (iParam) { - let { patientID } = iParam; - try { - let query = [ - { - $match: { - "00100020.Value": patientID - } - }, - { - $group: { - _id: "$studyUID", - pathList: { - $addToSet: { - studyUID: "$studyUID", - seriesUID: "$seriesUID", - instanceUID: "$instanceUID", - instancePath: "$instancePath" - } - } - } - } - ]; - let docs = await mongoose.model("dicom").aggregate(query).exec(); - let pathGroup = _.get(docs, "0.pathList", []); - - let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); - - return fullPathGroup; - - } catch (e) { - throw e; - } - } + deleteStatus: { + type: Number, + default: 0 } - } + }, + patientSchemaOptions ); for (let tag in tagsNeedStore.Patient) { @@ -142,15 +72,6 @@ patientSchema.index({ "00100020": 1 }); - -function getPatientLevelFields() { - let fields = {}; - for (let tag in tagsOfRequiredMatching.Patient) { - fields[tag] = 1; - } - return fields; -} - let patientModel = mongoose.model( "patient", patientSchema, @@ -158,5 +79,3 @@ let patientModel = mongoose.model( ); module.exports = patientModel; - -module.exports.getPatientLevelFields = getPatientLevelFields; diff --git a/models/mongodb/schema/dicom.schema.js b/models/mongodb/schema/dicom.schema.js new file mode 100644 index 00000000..24ac0ecb --- /dev/null +++ b/models/mongodb/schema/dicom.schema.js @@ -0,0 +1,277 @@ +const fsP = require("fs/promises"); +const path = require("path"); +const mongoose = require("mongoose"); +const _ = require("lodash"); +const { raccoonConfig } = require("@root/config-class"); +const { logger } = require("@root/utils/logs/log"); +const { IncludeFieldsFactory, getStoreDicomFullPath, getStoreDicomFullPathGroup } = require("../service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); + +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + +const BaseDicomSchemaDef = { + studyUID: { + type: String, + default: void 0, + index: true, + required: true + }, + deleteStatus: { + type: Number, + default: 0 + } +}; + +class DicomSchemaOptionsFactory { + constructor() {} + + /** + * + * @param {"patient" |"study" | "series" | "instance"} level + * @param {typeof DocDicomJsonHandler} docDicomJsonHandlerType + * @returns + */ + static get(level, docDicomJsonHandlerType) { + return { + strict: true, + versionKey: false, + toObject: { + getters: true + }, + methods: { + incrementDeleteStatus: async function () { + this.deleteStatus = this.deleteStatus + 1; + await this.save(); + }, + deleteDicomInstances: async function() { + throw new Error("Not Implemented"); + }, + getAttributes: async function () { + let doc = this.toObject(); + delete doc._id; + delete doc.id; + + let jsonStr = JSON.stringify(doc); + return await Common.getAttributesFromJsonString(jsonStr); + } + }, + statics: { + getDimseResultCursor: async function (query, keys) { + return mongoose.model(DicomModelNames[level]).find(query, keys).setOptions({ + strictQuery: false + }) + .cursor(); + }, + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJson: async function (queryOptions) { + let projection = mongoose.model(DicomModelNames[level]).getDicomJsonProjection(queryOptions); + + try { + let docs = await mongoose.model(DicomModelNames[level]).find({ + ...queryOptions.query, + deleteStatus: { + $eq: 0 + } + }, projection) + .limit(queryOptions.limit) + .skip(queryOptions.skip) + .setOptions({ + strictQuery: false + }) + .exec(); + + let docDicomJsonHandler = new docDicomJsonHandlerType(docs, queryOptions); + let dicomJson = docDicomJsonHandler.get(); + + return dicomJson; + + } catch (e) { + throw e; + } + }, + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJsonProjection: function (queryOptions) { + throw new Error("Not Implemented"); + }, + /** + * + * @param {Object} iParam + * @param {string} iParam.studyUID + */ + getPathGroupOfInstances: async function (iParam) { + try { + let query = [ + { + ...mongoose.model(DicomModelNames[level]).getPathGroupQuery(iParam) + }, + { + ...mongoose.model(DicomModelNames[level]).getPathGroupQueryOptions() + } + ]; + let docs = await mongoose.model("dicom").aggregate(query).exec(); + let pathGroup = _.get(docs, "0.pathList", []); + + let fullPathGroup = getStoreDicomFullPathGroup(pathGroup); + + return fullPathGroup; + + } catch (e) { + throw e; + } + }, + getPathGroupQueryOptions: function (level) { + return { + $group: { + _id: PathGroupIdField[level], + pathList: { + $addToSet: { + studyUID: "$studyUID", + seriesUID: "$seriesUID", + instanceUID: "$instanceUID", + instancePath: "$instancePath" + } + } + } + }; + }, + getPathGroupQuery: function (iParam) { + throw new Error("Not Implemented"); + } + }, + timestamps: true + }; + } +} + +const PathGroupIdField = Object.freeze({ + "patient": "$patientID", + "study": "$studyUID", + "series": "$seriesUID", + "instance": "$instanceUID" +}); + +const DicomModelNames = Object.freeze({ + "patient": "patient", + "study": "dicomStudy", + "series": "dicomSeries", + "instance": "dicom" +}); + +class DocDicomJsonHandler { + constructor(docs, queryOptions) { + this.docs = docs; + this.queryOptions = queryOptions; + } + + /** + * @private + * @param {any} obj + * @returns + */ + getPreprocessedDoc_(obj) { + let preProcessedDoc = obj.toObject(); + delete preProcessedDoc._id; + delete preProcessedDoc.id; + return preProcessedDoc; + } + + get() { + if (this.docs) { + return this.docs?.map((v) => { + let obj = this.getPreprocessedDoc_(v); + + this.setRetrieveUrl(obj); + this.setRetrieveAETitle(obj); + + return obj; + }); + } + return []; + } + + setRetrieveUrl(obj) { + _.set(obj, dictionary.keyword.RetrieveURL, { + ...dictionary.tagVR[dictionary.keyword.RetrieveURL], + Value: this.getRetrieveUrlValue(obj) + }); + } + + getRetrieveUrlValue(obj) { + throw new Error("Not Implemented"); + } + + setRetrieveAETitle(obj) { + _.set(obj, dictionary.keyword.RetrieveAETitle, { + ...dictionary.tagVR[dictionary.keyword.RetrieveAETitle], + Value: [raccoonConfig.aeTitle] + }); + } +} + +class PatientDocDicomJsonHandler extends DocDicomJsonHandler { + constructor (docs, queryOptions) { + super(docs, queryOptions); + } + + setRetrieveUrl(obj) { + return; + } + + setRetrieveAETitle(obj) { + return; + } +} + +class StudyDocDicomJsonHandler extends DocDicomJsonHandler { + constructor (docs, queryOptions) { + super(docs, queryOptions); + } + + getRetrieveUrlValue(obj) { + return [`${this.queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}`]; + } +} + +class SeriesDocDicomJsonHandler extends DocDicomJsonHandler { + constructor (docs, queryOptions) { + super(docs, queryOptions); + } + + getRetrieveUrlValue(obj) { + return [ + `${this.queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}` + ]; + } +} + +class InstanceDocDicomJsonHandler extends DocDicomJsonHandler { + constructor (docs, queryOptions) { + super(docs, queryOptions); + } + + getRetrieveUrlValue(obj) { + return [ + `${this.queryOptions.retrieveBaseUrl}/${obj["0020000D"]["Value"][0]}/series/${obj["0020000E"]["Value"][0]}/instances/${obj["00080018"]["Value"][0]}` + ]; + } +} + + +module.exports.BaseDicomSchemaDef = BaseDicomSchemaDef; +module.exports.DicomSchemaOptionsFactory = DicomSchemaOptionsFactory; +module.exports.PatientDocDicomJsonHandler = PatientDocDicomJsonHandler; +module.exports.StudyDocDicomJsonHandler = StudyDocDicomJsonHandler; +module.exports.SeriesDocDicomJsonHandler = SeriesDocDicomJsonHandler; +module.exports.InstanceDocDicomJsonHandler = InstanceDocDicomJsonHandler; \ No newline at end of file From 04de642c694da53771e5138b227037d3918d140d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 5 Nov 2023 10:01:53 +0800 Subject: [PATCH 101/365] test: remove expand field of patient --- test/patient.mongo.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/patient.mongo.test.js b/test/patient.mongo.test.js index 3e4e5ed1..fff93fdd 100644 --- a/test/patient.mongo.test.js +++ b/test/patient.mongo.test.js @@ -88,6 +88,9 @@ describe("Patient MongoDB and DicomJsonModel", async() => { let docObj = findDoc.toObject(); delete docObj._id; delete docObj.id; + delete docObj.deleteStatus; + delete docObj.createdAt; + delete docObj.updatedAt; expect(docObj).to.deep.equal(fakePatientData); }); From fa95e671d8829be5901289d390a2bbe70804410a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 5 Nov 2023 10:06:10 +0800 Subject: [PATCH 102/365] chore: ignore test folder for jscpd --- .jscpd.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.jscpd.json b/.jscpd.json index ed62b12d..da1246e7 100644 --- a/.jscpd.json +++ b/.jscpd.json @@ -8,7 +8,8 @@ "**/node_modules/**", "models/DICOM/dcm4che/wrapper/**/*.ts", "models/DICOM/dcm4che/wrapper/**/*.js", - "docs/**" + "docs/**", + "test/**" ], "absolute": true, "gitignore": true From 40e4fd3000a29e87937a58d26f33589952eed80a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 10 Nov 2023 21:56:03 +0800 Subject: [PATCH 103/365] fix: ths tls is not applied cause handshake_failed - Condition incorrect - If we have keystore that should applied tls setting - remove `!` not operator --- dimse/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimse/index.js b/dimse/index.js index 5b39ae58..fcf9ee3a 100644 --- a/dimse/index.js +++ b/dimse/index.js @@ -227,7 +227,7 @@ class DcmQrScp { let device = this.connection.getDeviceSync(); try { - if (!raccoonConfig.dicomDimseConfig.keyStore) { + if (raccoonConfig.dicomDimseConfig.keyStore) { device.setKeyManagerSync( SSLManagerFactory.createKeyManagerSync( raccoonConfig.dicomDimseConfig.keyStoreType, From b8d9dfdfe48c1c1c87e2a17e1753728169c9d553 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 10 Nov 2023 23:07:20 +0800 Subject: [PATCH 104/365] refactor: export `DicomToJpegTaskModel` --- .../STOW-RS/service/dicom-jpeg-generator.js | 16 ++++++++-------- jsconfig.json | 3 ++- models/mongodb/models/dicomToJpegTask.js | 1 + package.json | 3 ++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js index 95fd4c5c..3d7e38df 100644 --- a/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js +++ b/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js @@ -1,9 +1,9 @@ const fs = require("fs"); -const { Dcm2JpgExecutor } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); -const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); -const notImageSOPClass = require("../../../../../models/DICOM/dicomWEB/notImageSOPClass"); -const { logger } = require("../../../../../utils/logs/log"); -const dicomToJpegTask = require("../../../../../models/mongodb/models/dicomToJpegTask"); +const { Dcm2JpgExecutor } = require("@models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); +const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("@models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); +const notImageSOPClass = require("@models/DICOM/dicomWEB/notImageSOPClass"); +const { logger } = require("@root/utils/logs/log"); +const { DicomToJpegTaskModel } = require("@dbModels/dicomToJpegTask.js"); const colorette = require("colorette"); /** @@ -106,7 +106,7 @@ class DicomJpegGenerator { fileSize: `${(fs.statSync(this.dicomInstanceFilename).size / 1024 / 1024).toFixed(3)}MB` }; - await dicomToJpegTask.insertOrUpdate(startTaskObj); + await DicomToJpegTaskModel.insertOrUpdate(startTaskObj); } /** @@ -121,7 +121,7 @@ class DicomJpegGenerator { message: "generated", finishedTime: new Date() }; - await dicomToJpegTask.insertOrUpdate(endTaskObj); + await DicomToJpegTaskModel.insertOrUpdate(endTaskObj); } /** @@ -137,7 +137,7 @@ class DicomJpegGenerator { message: message, finishedTime: new Date() }; - await dicomToJpegTask.insertOrUpdate(errorTaskObj); + await DicomToJpegTaskModel.insertOrUpdate(errorTaskObj); } } diff --git a/jsconfig.json b/jsconfig.json index ef54b660..3226e465 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -7,7 +7,8 @@ "@models/*": ["./models/*"], "@error/*" : ["./error/*"], "@root/*": ["./*"], - "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"] + "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], + "@dbModels/*": ["./models/mongodb/models/*"] } }, "exclude": [ diff --git a/models/mongodb/models/dicomToJpegTask.js b/models/mongodb/models/dicomToJpegTask.js index cbc5272f..03dd309e 100644 --- a/models/mongodb/models/dicomToJpegTask.js +++ b/models/mongodb/models/dicomToJpegTask.js @@ -58,4 +58,5 @@ async function insertOrUpdate(item) { } module.exports = dicomToJpegTask; +module.exports.DicomToJpegTaskModel = dicomToJpegTask; module.exports.insertOrUpdate = insertOrUpdate; diff --git a/package.json b/package.json index 3d17daf8..ba37167a 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "@models": "./models", "@error": "./error", "@root": "./", - "@chinlinlee": "./models/DICOM/dcm4che/wrapper/org/github/chinlinlee" + "@chinlinlee": "./models/DICOM/dcm4che/wrapper/org/github/chinlinlee", + "@dbModels": "./models/mongodb/models" }, "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", From f332628c169a1024a2b526e651ae19171ce492b4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 10 Nov 2023 23:21:01 +0800 Subject: [PATCH 105/365] refactor: add db initializer module alias --- app.js | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 6f195df9..2dd275d3 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,4 @@ -const mongodb = require("./models/mongodb/index"); +require("@dbInitializer"); const express = require("express"); const { createServer } = require("http"); const app = express(); diff --git a/package.json b/package.json index ba37167a..72b5ae52 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "@error": "./error", "@root": "./", "@chinlinlee": "./models/DICOM/dcm4che/wrapper/org/github/chinlinlee", - "@dbModels": "./models/mongodb/models" + "@dbModels": "./models/mongodb/models", + "@dbInitializer": "./models/mongodb/index.js" }, "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", From c126e921ea61bd0be9540cb3d9502e1ad7687c24 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 10 Nov 2023 23:29:59 +0800 Subject: [PATCH 106/365] refactor: add module alias for `dicom-json-model` --- api/dicom-web/controller/STOW-RS/service/stow-rs.service.js | 2 +- .../controller/UPS-RS/service/base-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/cancel.service.js | 2 +- .../controller/UPS-RS/service/change-workItem-state.service.js | 2 +- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/subscribe.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js | 2 +- .../controller/UPS-RS/service/update-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/workItem-event.js | 2 +- jsconfig.json | 3 ++- package.json | 3 ++- 11 files changed, 13 insertions(+), 11 deletions(-) diff --git a/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js index 36bac7a0..241ff27b 100644 --- a/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ b/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -4,7 +4,7 @@ const { DicomJsonParser } = require("../../../../../models/DICOM/dicom-json-pars const { DicomJsonModel, DicomJsonBinaryDataModel -} = require("../../../../../models/DICOM/dicom-json-model"); +} = require("@dicom-json-model"); const { DicomFileSaver } = require("./dicom-file-saver"); const { DicomFhirService } = require("./dicom-fhir.service"); const { DicomJpegGenerator } = require("./dicom-jpeg-generator"); diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 8b5ddec3..8c60c0b9 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const { WorkItemEvent } = require("./workItem-event"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel } = require("@dicom-json-model"); const { findWsArrayByAeTitle } = require("@root/websocket"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/QIDO-RS.service"); diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index 4391971c..9d9c04b7 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const workItemModel = require("@models/mongodb/models/workItems"); -const { DicomJsonModel, BaseDicomJson } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel, BaseDicomJson } = require("@dicom-json-model"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { DicomWebServiceError, diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index 268c76bc..f1cef508 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const moment = require("moment"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); const workItemModel = require("@models/mongodb/models/workItems"); const { diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 90e1efea..6ab60ef6 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -6,7 +6,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel } = require("@dicom-json-model"); const { BaseWorkItemService } = require("./base-workItem.service"); const { SubscribeService } = require("./subscribe.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index e0abe7a0..ece30b23 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); const workItemModel = require("@models/mongodb/models/workItems"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index cfe13a3f..3848463f 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); const workItemModel = require("@models/mongodb/models/workItems"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 1ced5acc..2650e6fd 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -6,7 +6,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel } = require("@dicom-json-model"); const { BaseWorkItemService } = require("./base-workItem.service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); diff --git a/api/dicom-web/controller/UPS-RS/service/workItem-event.js b/api/dicom-web/controller/UPS-RS/service/workItem-event.js index 37a36322..6914170d 100644 --- a/api/dicom-web/controller/UPS-RS/service/workItem-event.js +++ b/api/dicom-web/controller/UPS-RS/service/workItem-event.js @@ -1,5 +1,5 @@ const { default: UID } = require("@dcm4che/data/UID"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { DicomJsonModel } = require("@dicom-json-model"); class WorkItemEvent { diff --git a/jsconfig.json b/jsconfig.json index 3226e465..1151bfbb 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -8,7 +8,8 @@ "@error/*" : ["./error/*"], "@root/*": ["./*"], "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], - "@dbModels/*": ["./models/mongodb/models/*"] + "@dbModels/*": ["./models/mongodb/models/*"], + "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"] } }, "exclude": [ diff --git a/package.json b/package.json index 72b5ae52..e798d3a7 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "@root": "./", "@chinlinlee": "./models/DICOM/dcm4che/wrapper/org/github/chinlinlee", "@dbModels": "./models/mongodb/models", - "@dbInitializer": "./models/mongodb/index.js" + "@dbInitializer": "./models/mongodb/index.js", + "@dicom-json-model": "./models/DICOM/dicom-json-model.js" }, "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", From 7aa2f33ae083a1532e45ea1212a315e8eabcb086 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 10 Nov 2023 23:52:29 +0800 Subject: [PATCH 107/365] refactor: require `PatientModel` instead of `patientModel` --- .../service/query-dicom-json-factory.js | 55 ++++++++++--------- .../UPS-RS/service/create-workItem.service.js | 10 ++-- .../UPS-RS/service/update-workItem.service.js | 2 +- dimse/patientQueryTask.js | 4 +- models/DICOM/dicom-json-model.js | 4 +- models/mongodb/models/patient.js | 2 + 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index ea1c19ee..83151c94 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const patientModel = require("@models/mongodb/models/patient"); +const { PatientModel } = require("@dbModels/patient"); const dicomStudyModel = require("@models/mongodb/models/dicomStudy"); const dicomSeriesModel = require("@models/mongodb/models/dicomSeries"); const dicomModel = require("@models/mongodb/models/dicom"); @@ -39,7 +39,7 @@ function commaValue(iKey, iValue) { function getWildCardQuery(value) { let wildCardIndex = value.indexOf("*"); let questionIndex = value.indexOf("?"); - + if (wildCardIndex >= 0 || questionIndex >= 0) { value = value.replace(/\*/gm, ".*"); value = value.replace(/\?/gm, "."); @@ -92,8 +92,8 @@ async function convertRequestQueryToMongoQuery(iQuery) { } return mongoQs.$match.$and.length == 0 ? { - $match: {} - } + $match: {} + } : mongoQs; } @@ -107,26 +107,27 @@ const vrQueryLookup = { PN: async (value, tag) => { let queryValue = _.cloneDeep(value[tag]); value[tag] = { - $or : [ - { - [`${tag}.Alphabetic`] : queryValue - }, - { - [`${tag}.familyName`] : queryValue - }, - { - [`${tag}.givenName`] : queryValue - } , - { - [`${tag}.middleName`] : queryValue - } , - { - [`${tag}.prefix`] : queryValue - }, - { - [`${tag}.suffix`] : queryValue - } - ]}; + $or: [ + { + [`${tag}.Alphabetic`]: queryValue + }, + { + [`${tag}.familyName`]: queryValue + }, + { + [`${tag}.givenName`]: queryValue + }, + { + [`${tag}.middleName`]: queryValue + }, + { + [`${tag}.prefix`]: queryValue + }, + { + [`${tag}.suffix`]: queryValue + } + ] + }; }, TM: async (value, tag) => { value[tag] = timeQuery(value, tag); @@ -150,7 +151,7 @@ function sortObjByFieldKey(obj) { class QueryDicomJsonFactory { constructor(queryOptions) { this.queryOptions = queryOptions; - this.model = patientModel; + this.model = PatientModel; } async getProcessedQueryOptions() { @@ -160,7 +161,7 @@ class QueryDicomJsonFactory { ...this.queryOptions.requestParams, ...mongoQuery.$match }; - + this.queryOptions.query = { ...query }; return this.queryOptions; } @@ -178,7 +179,7 @@ class QueryDicomJsonFactory { class QueryPatientDicomJsonFactory extends QueryDicomJsonFactory { constructor(queryOptions) { super(queryOptions); - this.model = patientModel; + this.model = PatientModel; } } diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 6ab60ef6..c27aef2a 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const workItemModel = require("@models/mongodb/models/workItems"); -const patientModel = require("@models/mongodb/models/patient"); +const { PatientModel } = require("@dbModels/patient"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { DicomWebServiceError, @@ -98,14 +98,14 @@ class CreateWorkItemService extends BaseWorkItemService { let patientId = this.requestWorkItem.getString("00100020"); _.set(this.requestWorkItem.dicomJson, "patientID", patientId); - /** @type {patientModel | null} */ - let patient = await patientModel.findOne({ + /** @type {PatientModel | null} */ + let patient = await PatientModel.findOne({ "00100020.Value": patientId }); if (!patient) { - /** @type {patientModel} */ - let patientObj = new patientModel(this.requestWorkItem.dicomJson); + /** @type {PatientModel} */ + let patientObj = new PatientModel(this.requestWorkItem.dicomJson); patient = await patientObj.save(); } diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 2650e6fd..148cb321 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const workItemModel = require("@models/mongodb/models/workItems"); -const patientModel = require("@models/mongodb/models/patient"); +const { PatientModel } = require("@dbModels/patient"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { DicomWebServiceError, diff --git a/dimse/patientQueryTask.js b/dimse/patientQueryTask.js index 90e7a1d3..32cbc5e7 100644 --- a/dimse/patientQueryTask.js +++ b/dimse/patientQueryTask.js @@ -6,7 +6,7 @@ const { Attributes } = require("@dcm4che/data/Attributes"); const { Tag } = require("@dcm4che/data/Tag"); const { VR } = require("@dcm4che/data/VR"); const { DimseQueryBuilder } = require("./queryBuilder"); -const patientModel = require("@models/mongodb/models/patient"); +const { PatientModel } = require("@dbModels/patient"); const { Association } = require("@dcm4che/net/Association"); const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); const { logger } = require("@root/utils/logs/log"); @@ -162,7 +162,7 @@ class JsPatientQueryTask { let returnKeys = this.getReturnKeys(normalQuery); logger.info(`do DIMSE Patient query: ${JSON.stringify(mongoQuery.$match)}`); - this.cursor = await patientModel.getDimseResultCursor({ + this.cursor = await PatientModel.getDimseResultCursor({ ...mongoQuery.$match }, returnKeys); } diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 0b978ba9..18fb5019 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -11,7 +11,7 @@ const flat = require("flat"); const shortHash = require("shorthash2"); const dicomBulkDataModel = require("../mongodb/models/dicomBulkData"); const { logger } = require("../../utils/logs/log"); -const patientModel = require("../mongodb/models/patient"); +const { PatientModel } = require("@dbModels/patient"); const { tagsNeedStore } = require("./dicom-tags-mapping"); const { raccoonConfig } = require("../../config-class"); @@ -214,7 +214,7 @@ class DicomJsonModel { } async storePatientCollection(dicomJson) { - await patientModel.findOneAndUpdate( + await PatientModel.findOneAndUpdate( { patientID: this.uidObj.patientID }, diff --git a/models/mongodb/models/patient.js b/models/mongodb/models/patient.js index 79a4b5f9..97e18dfe 100644 --- a/models/mongodb/models/patient.js +++ b/models/mongodb/models/patient.js @@ -79,3 +79,5 @@ let patientModel = mongoose.model( ); module.exports = patientModel; + +module.exports.PatientModel = patientModel; From 2075c1d98fe99d8c74b56f548d393c68377136af Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 10 Nov 2023 23:55:13 +0800 Subject: [PATCH 108/365] refactor: require `StudyModel` instead of `dicomStudyModel` --- .../controller/QIDO-RS/service/query-dicom-json-factory.js | 4 ++-- api/dicom-web/controller/WADO-RS/deletion/service/delete.js | 4 ++-- dimse/studyQueryTask.js | 4 ++-- models/mongodb/deleteSchedule.js | 4 ++-- models/mongodb/models/dicomStudy.js | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index 83151c94..f3e892e0 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const { PatientModel } = require("@dbModels/patient"); -const dicomStudyModel = require("@models/mongodb/models/dicomStudy"); +const { StudyModel } = require("@dbModels/dicomStudy"); const dicomSeriesModel = require("@models/mongodb/models/dicomSeries"); const dicomModel = require("@models/mongodb/models/dicom"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); @@ -186,7 +186,7 @@ class QueryPatientDicomJsonFactory extends QueryDicomJsonFactory { class QueryStudyDicomJsonFactory extends QueryDicomJsonFactory { constructor(queryOptions) { super(queryOptions); - this.model = dicomStudyModel; + this.model = StudyModel; } } diff --git a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js index d8129f68..5869a709 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const dicomStudyModel = require("../../../../../../models/mongodb/models/dicomStudy"); +const { StudyModel } = require("@dbModels/dicomStudy"); const dicomSeriesModel = require("../../../../../../models/mongodb/models/dicomSeries"); const dicomModel = require("../../../../../../models/mongodb/models/dicom"); const fsP = require("fs/promises"); @@ -28,7 +28,7 @@ class DeleteService { } async deleteStudy() { - let study = await dicomStudyModel.findOne({ + let study = await StudyModel.findOne({ ...this.request.params }); diff --git a/dimse/studyQueryTask.js b/dimse/studyQueryTask.js index 87ea9c9a..c279ea16 100644 --- a/dimse/studyQueryTask.js +++ b/dimse/studyQueryTask.js @@ -6,7 +6,7 @@ const { Tag } = require("@dcm4che/data/Tag"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); const { StudyQueryTaskInjectInterface, createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); const { DimseQueryBuilder } = require("./queryBuilder"); -const dicomStudyModel = require("@models/mongodb/models/dicomStudy"); +const { StudyModel } = require("@dbModels/dicomStudy"); const { Attributes } = require("@dcm4che/data/Attributes"); const { logger } = require("@root/utils/logs/log"); const { AuditManager } = require("@models/DICOM/audit/auditManager"); @@ -132,7 +132,7 @@ class JsStudyQueryTask extends JsPatientQueryTask { let returnKeys = this.getReturnKeys(normalQuery); logger.info(`do DIMSE Study query: ${JSON.stringify(mongoQuery.$match)}`); - this.studyCursor = await dicomStudyModel.getDimseResultCursor({ + this.studyCursor = await StudyModel.getDimseResultCursor({ ...mongoQuery.$match }, returnKeys); } diff --git a/models/mongodb/deleteSchedule.js b/models/mongodb/deleteSchedule.js index 18704fcb..517094e8 100644 --- a/models/mongodb/deleteSchedule.js +++ b/models/mongodb/deleteSchedule.js @@ -1,7 +1,7 @@ const schedule = require("node-schedule"); const moment = require("moment"); const { logger } = require("@root/utils/logs/log"); -const dicomStudyModel = require("./models/dicomStudy"); +const { StudyModel } = require("@dbModels/dicomStudy"); const dicomModel = require("./models/dicom"); const dicomSeriesModel = require("./models/dicomSeries"); @@ -20,7 +20,7 @@ schedule.scheduleJob("*/5 * * * * *", async function () { async function deleteExpireStudies() { - let deletedStudies = await dicomStudyModel.find({ + let deletedStudies = await StudyModel.find({ deleteStatus: { $gte: 2 } diff --git a/models/mongodb/models/dicomStudy.js b/models/mongodb/models/dicomStudy.js index 6bc5fcca..9d804de6 100644 --- a/models/mongodb/models/dicomStudy.js +++ b/models/mongodb/models/dicomStudy.js @@ -81,3 +81,4 @@ let dicomStudyModel = mongoose.model( ); module.exports = dicomStudyModel; +module.exports.StudyModel = dicomStudyModel; From 33bdee2c8d853159f37b4745f5969b3da0bb7cb3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 10 Nov 2023 23:58:17 +0800 Subject: [PATCH 109/365] refactor: require `SeriesModel` instead of `dicomSeriesModel` --- .../QIDO-RS/service/query-dicom-json-factory.js | 4 ++-- .../controller/WADO-RS/deletion/service/delete.js | 4 ++-- .../controller/WADO-RS/service/thumbnail.service.js | 1 - dimse/seriesQueryTask.js | 6 +++--- models/mongodb/deleteSchedule.js | 10 +++++----- models/mongodb/models/dicomSeries.js | 1 + 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index f3e892e0..b58a11e9 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { PatientModel } = require("@dbModels/patient"); const { StudyModel } = require("@dbModels/dicomStudy"); -const dicomSeriesModel = require("@models/mongodb/models/dicomSeries"); +const { SeriesModel } = require("@dbModels/dicomSeries"); const dicomModel = require("@models/mongodb/models/dicom"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { mongoDateQuery, timeQuery } = require("@models/mongodb/service"); @@ -193,7 +193,7 @@ class QueryStudyDicomJsonFactory extends QueryDicomJsonFactory { class QuerySeriesDicomJsonFactory extends QueryDicomJsonFactory { constructor(queryOptions) { super(queryOptions); - this.model = dicomSeriesModel; + this.model = SeriesModel; } } diff --git a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js index 5869a709..e74580ca 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const { StudyModel } = require("@dbModels/dicomStudy"); -const dicomSeriesModel = require("../../../../../../models/mongodb/models/dicomSeries"); +const { SeriesModel } = require("@dbModels/dicomSeries"); const dicomModel = require("../../../../../../models/mongodb/models/dicom"); const fsP = require("fs/promises"); const { NotFoundInstanceError } = require("../../../../../../error/dicom-instance"); @@ -46,7 +46,7 @@ class DeleteService { } async deleteSeries() { - let aSeries = await dicomSeriesModel.findOne({ + let aSeries = await SeriesModel.findOne({ ...this.request.params }); diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 2b629393..7880acb8 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,5 +1,4 @@ const dicomModel = require("../../../../../models/mongodb/models/dicom"); -const dicomSeriesModel = require("../../../../../models/mongodb/models/dicomSeries"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const renderedService = require("../service/rendered.service"); const _ = require("lodash"); diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index 9cdc11d1..2208b440 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -3,7 +3,7 @@ const _ = require("lodash"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); const { DimseQueryBuilder } = require("./queryBuilder"); const { JsStudyQueryTask } = require("./studyQueryTask"); -const dicomSeriesModel = require("@models/mongodb/models/dicomSeries"); +const { SeriesModel } = require("@dbModels/dicomSeries"); const { SeriesQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTask"); const { Attributes } = require("@dcm4che/data/Attributes"); const { createSeriesQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTaskInject"); @@ -117,7 +117,7 @@ class JsSeriesQueryTask extends JsStudyQueryTask { await this.as.getRemoteAET(), await this.as.getRemoteHostName(), await this.as.getLocalAET(), await this.as.getLocalHostName() ); - + let queryAttr = await Attributes.newInstanceAsync(); await queryAttr.addAll(this.keys); await queryAttr.addSelected(this.studyAttr, [Tag.PatientID, Tag.StudyInstanceUID]); @@ -134,7 +134,7 @@ class JsSeriesQueryTask extends JsStudyQueryTask { let returnKeys = this.getReturnKeys(normalQuery); logger.info(`do DIMSE Series query: ${JSON.stringify(mongoQuery.$match)}`); - this.seriesCursor = await dicomSeriesModel.getDimseResultCursor({ + this.seriesCursor = await SeriesModel.getDimseResultCursor({ ...mongoQuery.$match }, returnKeys); } diff --git a/models/mongodb/deleteSchedule.js b/models/mongodb/deleteSchedule.js index 517094e8..1240f9cb 100644 --- a/models/mongodb/deleteSchedule.js +++ b/models/mongodb/deleteSchedule.js @@ -3,7 +3,7 @@ const moment = require("moment"); const { logger } = require("@root/utils/logs/log"); const { StudyModel } = require("@dbModels/dicomStudy"); const dicomModel = require("./models/dicom"); -const dicomSeriesModel = require("./models/dicomSeries"); +const { SeriesModel } = require("@dbModels/dicomSeries"); // Delete dicom with delete status >= 2 schedule.scheduleJob("*/5 * * * * *", async function () { @@ -38,7 +38,7 @@ async function deleteExpireStudies() { dicomModel.deleteMany({ studyUID }), - dicomSeriesModel.deleteMany({ + SeriesModel.deleteMany({ studyUID }), deletedStudy.delete() @@ -50,7 +50,7 @@ async function deleteExpireStudies() { } async function deleteExpireSeries() { - let deletedSeries = await dicomSeriesModel.find({ + let deletedSeries = await SeriesModel.find({ deleteStatus: { $gte: 2 } @@ -61,7 +61,7 @@ async function deleteExpireSeries() { let now = moment(); let diff = now.diff(updateAtDate, "seconds"); if (diff >= 30) { - let {studyUID, seriesUID} = aDeletedSeries; + let { studyUID, seriesUID } = aDeletedSeries; logger.info("delete expired series: " + seriesUID); await Promise.all([ @@ -87,7 +87,7 @@ async function deleteExpireInstances() { }); for (let deletedInstance of deletedInstances) { - let {instanceUID} = deletedInstance; + let { instanceUID } = deletedInstance; let updateAtDate = moment(deletedInstance.updatedAt); let now = moment(); diff --git a/models/mongodb/models/dicomSeries.js b/models/mongodb/models/dicomSeries.js index 6529cf20..7d13a295 100644 --- a/models/mongodb/models/dicomSeries.js +++ b/models/mongodb/models/dicomSeries.js @@ -107,3 +107,4 @@ let dicomSeriesModel = mongoose.model( ); module.exports = dicomSeriesModel; +module.exports.SeriesModel = dicomSeriesModel; From ceeb7b05747a8fc55e4dfbcd29c0bc361a5d49d3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:03:04 +0800 Subject: [PATCH 110/365] refactor: require `InstanceModel` instead of `dicomModel` --- api/WADO-URI/service/WADO-URI.service.js | 4 ++-- .../controller/QIDO-RS/service/query-dicom-json-factory.js | 4 ++-- api/dicom-web/controller/WADO-RS/bulkdata/instance.js | 6 +++--- api/dicom-web/controller/WADO-RS/deletion/service/delete.js | 4 ++-- api/dicom-web/controller/WADO-RS/service/WADOZip.js | 5 ++--- .../controller/WADO-RS/service/thumbnail.service.js | 6 +++--- dimse/instanceQueryTask.js | 4 ++-- models/mongodb/models/dicom.js | 1 + 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 05f83b2e..4a36c4f1 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -7,7 +7,7 @@ const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../models/DICOM/dcm4ch const sharp = require('sharp'); const Magick = require("../../../models/magick"); const { NotFoundInstanceError, InvalidFrameNumberError, InstanceGoneError } = require("../../../error/dicom-instance"); -const dicomModel = require("../../../models/mongodb/models/dicom"); +const { InstanceModel } = require("@dbModels/dicom"); const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); @@ -127,7 +127,7 @@ class WadoUriService { objectUID: instanceUID } = this.request.query; - let imagePathObj = await dicomModel.getPathOfInstance({ + let imagePathObj = await InstanceModel.getPathOfInstance({ studyUID, seriesUID, instanceUID diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index b58a11e9..de8259ea 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const { PatientModel } = require("@dbModels/patient"); const { StudyModel } = require("@dbModels/dicomStudy"); const { SeriesModel } = require("@dbModels/dicomSeries"); -const dicomModel = require("@models/mongodb/models/dicom"); +const { InstanceModel } = require("@dbModels/dicom"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { mongoDateQuery, timeQuery } = require("@models/mongodb/service"); @@ -200,7 +200,7 @@ class QuerySeriesDicomJsonFactory extends QueryDicomJsonFactory { class QueryInstanceDicomJsonFactory extends QueryDicomJsonFactory { constructor(queryOptions) { super(queryOptions); - this.model = dicomModel; + this.model = InstanceModel; } } diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js index dc51e844..5347aafe 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js @@ -1,7 +1,7 @@ const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { BulkDataService, InstanceBulkDataFactory } = require("./service/bulkdata"); const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); -const dicomModel = require("../../../../../models/mongodb/models/dicom"); +const { InstanceModel } = require("@dbModels/dicom"); const { BaseBulkDataController } = require("./base.controller"); const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); @@ -11,7 +11,7 @@ class InstanceBulkDataController extends BaseBulkDataController { this.bulkDataFactoryType = InstanceBulkDataFactory; this.imagePathFactoryType = InstanceImagePathFactory; } - + logAction() { this.apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ , SeriesInstanceUID: ${this.request.params.seriesUID}\ @@ -27,7 +27,7 @@ class InstanceBulkDataController extends BaseBulkDataController { * @param {import("express").Response} * @returns */ -module.exports = async function(req, res) { +module.exports = async function (req, res) { let instanceBulkDataController = new InstanceBulkDataController(req, res); await instanceBulkDataController.doPipeline(); diff --git a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js index e74580ca..3cf5e1c7 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { StudyModel } = require("@dbModels/dicomStudy"); const { SeriesModel } = require("@dbModels/dicomSeries"); -const dicomModel = require("../../../../../../models/mongodb/models/dicom"); +const { InstanceModel } = require("@dbModels/dicom"); const fsP = require("fs/promises"); const { NotFoundInstanceError } = require("../../../../../../error/dicom-instance"); @@ -64,7 +64,7 @@ class DeleteService { async deleteInstance() { - let instance = await dicomModel.findOne({ + let instance = await InstanceModel.findOne({ ...this.request.params }); diff --git a/api/dicom-web/controller/WADO-RS/service/WADOZip.js b/api/dicom-web/controller/WADO-RS/service/WADOZip.js index 9e887efa..6a64719b 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADOZip.js +++ b/api/dicom-web/controller/WADO-RS/service/WADOZip.js @@ -2,8 +2,7 @@ const mongoose = require("mongoose"); const archiver = require("archiver"); const wadoService = require("./WADO-RS.service"); const path = require("path"); -const dicomModel = require("../../../../../models/mongodb/models/dicom"); - +const { InstanceModel } = require("@dbModels/dicom"); class WADOZip { constructor(iReq, iRes) { this.requestParams = iReq.params; @@ -58,7 +57,7 @@ class WADOZip { } async getZipOfInstanceDICOMFile() { - let imagePath = await dicomModel.getPathOfInstance(this.requestParams); + let imagePath = await InstanceModel.getPathOfInstance(this.requestParams); if (imagePath) { this.setHeaders(this.instanceUID); diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 7880acb8..aab7c75e 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,4 +1,4 @@ -const dicomModel = require("../../../../../models/mongodb/models/dicom"); +const { InstanceModel } = require("@dbModels/dicom"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const renderedService = require("../service/rendered.service"); const _ = require("lodash"); @@ -94,7 +94,7 @@ class StudyThumbnailFactory extends ThumbnailFactory { * @param {import("../../../../../utils/typeDef/dicom").Uids} uids */ async getThumbnailInstance() { - let medianInstance = await dicomModel.getInstanceOfMedianIndex({ + let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ studyUID: this.uids.studyUID }); if (!medianInstance) return undefined; @@ -120,7 +120,7 @@ class SeriesThumbnailFactory extends ThumbnailFactory { * @param {import("../../../../../utils/typeDef/dicom").Uids} uids */ async getThumbnailInstance() { - let medianInstance = await dicomModel.getInstanceOfMedianIndex({ + let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ studyUID: this.uids.studyUID, seriesUID: this.uids.seriesUID }); diff --git a/dimse/instanceQueryTask.js b/dimse/instanceQueryTask.js index 13cda2bd..c636857c 100644 --- a/dimse/instanceQueryTask.js +++ b/dimse/instanceQueryTask.js @@ -3,7 +3,7 @@ const _ = require("lodash"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); const { DimseQueryBuilder } = require("./queryBuilder"); const { JsSeriesQueryTask } = require("./seriesQueryTask"); -const dicomModel = require("@models/mongodb/models/dicom"); +const { InstanceModel } = require("@dbModels/dicom"); const { InstanceQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTask"); const { Attributes } = require("@dcm4che/data/Attributes"); const { createInstanceQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTaskInject"); @@ -136,7 +136,7 @@ class JsInstanceQueryTask extends JsSeriesQueryTask { let returnKeys = this.getReturnKeys(normalQuery); logger.info(`do DIMSE Instance query: ${JSON.stringify(mongoQuery.$match)}`); - this.instanceCursor = await dicomModel.getDimseResultCursor({ + this.instanceCursor = await InstanceModel.getDimseResultCursor({ ...mongoQuery.$match }, returnKeys); } diff --git a/models/mongodb/models/dicom.js b/models/mongodb/models/dicom.js index 8ed118c9..ce59ca29 100644 --- a/models/mongodb/models/dicom.js +++ b/models/mongodb/models/dicom.js @@ -474,5 +474,6 @@ async function updateStudyNumberOfStudyRelatedInstance(doc) { let dicomModel = mongoose.model("dicom", dicomModelSchema, "dicom"); module.exports = dicomModel; +module.exports.InstanceModel = dicomModel; module.exports.getModalitiesInStudy = getModalitiesInStudy; From 48ea411fe1d8c2f36ced876f2a602afbc39f5620 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:30:36 +0800 Subject: [PATCH 111/365] refactor: rename `dicom.js` to `instance.model.js` refactor: rename `dicom.js` to `instance.model.js` refactor: rename `dicom.js` to `instance.model.js` --- api/WADO-URI/service/WADO-URI.service.js | 2 +- .../QIDO-RS/service/query-dicom-json-factory.js | 2 +- .../controller/WADO-RS/bulkdata/instance.js | 2 +- .../controller/WADO-RS/deletion/service/delete.js | 2 +- .../controller/WADO-RS/service/WADO-RS.service.js | 4 ++-- api/dicom-web/controller/WADO-RS/service/WADOZip.js | 2 +- .../controller/WADO-RS/service/thumbnail.service.js | 2 +- dimse/instanceQueryTask.js | 2 +- models/FHIR/DICOM/DICOMToFHIR.js | 2 +- models/mongodb/deleteSchedule.js | 12 ++++++------ .../mongodb/models/{dicom.js => instance.model.js} | 0 11 files changed, 16 insertions(+), 16 deletions(-) rename models/mongodb/models/{dicom.js => instance.model.js} (100%) diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 4a36c4f1..77c71940 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -7,7 +7,7 @@ const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../models/DICOM/dcm4ch const sharp = require('sharp'); const Magick = require("../../../models/magick"); const { NotFoundInstanceError, InvalidFrameNumberError, InstanceGoneError } = require("../../../error/dicom-instance"); -const { InstanceModel } = require("@dbModels/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index de8259ea..e56a5706 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const { PatientModel } = require("@dbModels/patient"); const { StudyModel } = require("@dbModels/dicomStudy"); const { SeriesModel } = require("@dbModels/dicomSeries"); -const { InstanceModel } = require("@dbModels/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { mongoDateQuery, timeQuery } = require("@models/mongodb/service"); diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js index 5347aafe..b164cd33 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js @@ -1,7 +1,7 @@ const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { BulkDataService, InstanceBulkDataFactory } = require("./service/bulkdata"); const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { InstanceModel } = require("@dbModels/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); const { BaseBulkDataController } = require("./base.controller"); const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); diff --git a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js index 3cf5e1c7..5f25329f 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { StudyModel } = require("@dbModels/dicomStudy"); const { SeriesModel } = require("@dbModels/dicomSeries"); -const { InstanceModel } = require("@dbModels/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); const fsP = require("fs/promises"); const { NotFoundInstanceError } = require("../../../../../../error/dicom-instance"); diff --git a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 2c722aab..284fa5dc 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -7,7 +7,7 @@ const errorResponse = require("../../../../../utils/errorResponse/errorResponseM const { raccoonConfig } = require("../../../../../config-class"); const { JSONPath } = require("jsonpath-plus"); const { DicomWebService } = require("../../../service/dicom-web.service"); -const dicomModel = require("../../../../../models/mongodb/models/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); const { logger } = require("../../../../../utils/logs/log"); const { RetrieveAuditService } = require("./retrieveAudit.service"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); @@ -170,7 +170,7 @@ class InstanceImagePathFactory extends ImagePathFactory { } async getImagePaths() { - let imagePath = await dicomModel.getPathOfInstance(this.uids); + let imagePath = await InstanceModel.getPathOfInstance(this.uids); if (imagePath) this.imagePaths = [imagePath]; diff --git a/api/dicom-web/controller/WADO-RS/service/WADOZip.js b/api/dicom-web/controller/WADO-RS/service/WADOZip.js index 6a64719b..ab31df6f 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADOZip.js +++ b/api/dicom-web/controller/WADO-RS/service/WADOZip.js @@ -2,7 +2,7 @@ const mongoose = require("mongoose"); const archiver = require("archiver"); const wadoService = require("./WADO-RS.service"); const path = require("path"); -const { InstanceModel } = require("@dbModels/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); class WADOZip { constructor(iReq, iRes) { this.requestParams = iReq.params; diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index aab7c75e..4bed2b02 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,4 +1,4 @@ -const { InstanceModel } = require("@dbModels/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const renderedService = require("../service/rendered.service"); const _ = require("lodash"); diff --git a/dimse/instanceQueryTask.js b/dimse/instanceQueryTask.js index c636857c..996078b2 100644 --- a/dimse/instanceQueryTask.js +++ b/dimse/instanceQueryTask.js @@ -3,7 +3,7 @@ const _ = require("lodash"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); const { DimseQueryBuilder } = require("./queryBuilder"); const { JsSeriesQueryTask } = require("./seriesQueryTask"); -const { InstanceModel } = require("@dbModels/dicom"); +const { InstanceModel } = require("@dbModels/instance.model"); const { InstanceQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTask"); const { Attributes } = require("@dcm4che/data/Attributes"); const { createInstanceQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTaskInject"); diff --git a/models/FHIR/DICOM/DICOMToFHIR.js b/models/FHIR/DICOM/DICOMToFHIR.js index f3589431..e48c7466 100644 --- a/models/FHIR/DICOM/DICOMToFHIR.js +++ b/models/FHIR/DICOM/DICOMToFHIR.js @@ -6,7 +6,7 @@ const { fhirLogger } = require("../../../utils/logs/log"); const { dicomJsonToFHIRImagingStudy } = require("./DICOMToFHIRImagingStudy"); const { dicomJsonToFHIRPatient } = require("./DICOMToFHIRPatient"); const { dicomJsonToFHIREndpoint } = require("./DICOMToFHIREndpoint"); -const { getModalitiesInStudy } = require("../../../models/mongodb/models/dicom"); +const { getModalitiesInStudy } = require("@dbModels/instance.model"); class DICOMFHIRConverter { constructor() { diff --git a/models/mongodb/deleteSchedule.js b/models/mongodb/deleteSchedule.js index 1240f9cb..271301b7 100644 --- a/models/mongodb/deleteSchedule.js +++ b/models/mongodb/deleteSchedule.js @@ -1,9 +1,9 @@ const schedule = require("node-schedule"); const moment = require("moment"); const { logger } = require("@root/utils/logs/log"); -const { StudyModel } = require("@dbModels/dicomStudy"); -const dicomModel = require("./models/dicom"); -const { SeriesModel } = require("@dbModels/dicomSeries"); +const { StudyModel } = require("@dbModels/study.model"); +const { InstanceModel } = require("@dbModels/instance.model"); +const { SeriesModel } = require("@dbModels/series.model"); // Delete dicom with delete status >= 2 schedule.scheduleJob("*/5 * * * * *", async function () { @@ -35,7 +35,7 @@ async function deleteExpireStudies() { logger.info("delete expired study: " + studyUID); await Promise.all([ - dicomModel.deleteMany({ + InstanceModel.deleteMany({ studyUID }), SeriesModel.deleteMany({ @@ -65,7 +65,7 @@ async function deleteExpireSeries() { logger.info("delete expired series: " + seriesUID); await Promise.all([ - dicomModel.deleteMany({ + InstanceModel.deleteMany({ $and: [ { x0020000D: studyUID }, { x0020000E: seriesUID } @@ -80,7 +80,7 @@ async function deleteExpireSeries() { } async function deleteExpireInstances() { - let deletedInstances = await dicomModel.find({ + let deletedInstances = await InstanceModel.find({ deleteStatus: { $gte: 2 } diff --git a/models/mongodb/models/dicom.js b/models/mongodb/models/instance.model.js similarity index 100% rename from models/mongodb/models/dicom.js rename to models/mongodb/models/instance.model.js From 9e5b03e5d9c07154345b8092b18e198cdd5f2584 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:23:30 +0800 Subject: [PATCH 112/365] refactor: rename `patient.js` to `patient.model.js` --- .../controller/QIDO-RS/service/query-dicom-json-factory.js | 2 +- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- .../controller/UPS-RS/service/update-workItem.service.js | 2 +- dimse/patientQueryTask.js | 2 +- models/DICOM/dicom-json-model.js | 2 +- models/mongodb/models/{patient.js => patient.model.js} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename models/mongodb/models/{patient.js => patient.model.js} (100%) diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index e56a5706..193e8975 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { PatientModel } = require("@dbModels/patient"); +const { PatientModel } = require("@dbModels/patient.model"); const { StudyModel } = require("@dbModels/dicomStudy"); const { SeriesModel } = require("@dbModels/dicomSeries"); const { InstanceModel } = require("@dbModels/instance.model"); diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index c27aef2a..186ea403 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const workItemModel = require("@models/mongodb/models/workItems"); -const { PatientModel } = require("@dbModels/patient"); +const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { DicomWebServiceError, diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 148cb321..cfc9810e 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const workItemModel = require("@models/mongodb/models/workItems"); -const { PatientModel } = require("@dbModels/patient"); +const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { DicomWebServiceError, diff --git a/dimse/patientQueryTask.js b/dimse/patientQueryTask.js index 32cbc5e7..2af75893 100644 --- a/dimse/patientQueryTask.js +++ b/dimse/patientQueryTask.js @@ -6,7 +6,7 @@ const { Attributes } = require("@dcm4che/data/Attributes"); const { Tag } = require("@dcm4che/data/Tag"); const { VR } = require("@dcm4che/data/VR"); const { DimseQueryBuilder } = require("./queryBuilder"); -const { PatientModel } = require("@dbModels/patient"); +const { PatientModel } = require("@dbModels/patient.model"); const { Association } = require("@dcm4che/net/Association"); const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); const { logger } = require("@root/utils/logs/log"); diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 18fb5019..01990241 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -11,7 +11,7 @@ const flat = require("flat"); const shortHash = require("shorthash2"); const dicomBulkDataModel = require("../mongodb/models/dicomBulkData"); const { logger } = require("../../utils/logs/log"); -const { PatientModel } = require("@dbModels/patient"); +const { PatientModel } = require("@dbModels/patient.model"); const { tagsNeedStore } = require("./dicom-tags-mapping"); const { raccoonConfig } = require("../../config-class"); diff --git a/models/mongodb/models/patient.js b/models/mongodb/models/patient.model.js similarity index 100% rename from models/mongodb/models/patient.js rename to models/mongodb/models/patient.model.js From ca80375b2f0cb4c86e5f2c01e8ab3aebd7e38aec Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:24:12 +0800 Subject: [PATCH 113/365] refactor: rename `dicomStudy.js` to `study.model.js` --- .../controller/QIDO-RS/service/query-dicom-json-factory.js | 2 +- api/dicom-web/controller/WADO-RS/deletion/service/delete.js | 2 +- dimse/studyQueryTask.js | 2 +- models/mongodb/models/{dicomStudy.js => study.model.js} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename models/mongodb/models/{dicomStudy.js => study.model.js} (100%) diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index 193e8975..7f4f9349 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const { PatientModel } = require("@dbModels/patient.model"); -const { StudyModel } = require("@dbModels/dicomStudy"); +const { StudyModel } = require("@dbModels/study.model"); const { SeriesModel } = require("@dbModels/dicomSeries"); const { InstanceModel } = require("@dbModels/instance.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); diff --git a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js index 5f25329f..e66743ae 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { StudyModel } = require("@dbModels/dicomStudy"); +const { StudyModel } = require("@dbModels/study.model"); const { SeriesModel } = require("@dbModels/dicomSeries"); const { InstanceModel } = require("@dbModels/instance.model"); const fsP = require("fs/promises"); diff --git a/dimse/studyQueryTask.js b/dimse/studyQueryTask.js index c279ea16..a4e5e2b2 100644 --- a/dimse/studyQueryTask.js +++ b/dimse/studyQueryTask.js @@ -6,7 +6,7 @@ const { Tag } = require("@dcm4che/data/Tag"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); const { StudyQueryTaskInjectInterface, createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); const { DimseQueryBuilder } = require("./queryBuilder"); -const { StudyModel } = require("@dbModels/dicomStudy"); +const { StudyModel } = require("@dbModels/study.model"); const { Attributes } = require("@dcm4che/data/Attributes"); const { logger } = require("@root/utils/logs/log"); const { AuditManager } = require("@models/DICOM/audit/auditManager"); diff --git a/models/mongodb/models/dicomStudy.js b/models/mongodb/models/study.model.js similarity index 100% rename from models/mongodb/models/dicomStudy.js rename to models/mongodb/models/study.model.js From 285e300b6591944a6e23302062788e65cc5435ad Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:25:02 +0800 Subject: [PATCH 114/365] refactor: rename `dicomSeries.js` to `series.model.js` --- .../controller/QIDO-RS/service/query-dicom-json-factory.js | 2 +- api/dicom-web/controller/WADO-RS/deletion/service/delete.js | 2 +- dimse/seriesQueryTask.js | 2 +- models/mongodb/models/{dicomSeries.js => series.model.js} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename models/mongodb/models/{dicomSeries.js => series.model.js} (100%) diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index 7f4f9349..b123280c 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { PatientModel } = require("@dbModels/patient.model"); const { StudyModel } = require("@dbModels/study.model"); -const { SeriesModel } = require("@dbModels/dicomSeries"); +const { SeriesModel } = require("@dbModels/series.model"); const { InstanceModel } = require("@dbModels/instance.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { mongoDateQuery, timeQuery } = require("@models/mongodb/service"); diff --git a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js index e66743ae..9b4a33c7 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const { StudyModel } = require("@dbModels/study.model"); -const { SeriesModel } = require("@dbModels/dicomSeries"); +const { SeriesModel } = require("@dbModels/series.model"); const { InstanceModel } = require("@dbModels/instance.model"); const fsP = require("fs/promises"); const { NotFoundInstanceError } = require("../../../../../../error/dicom-instance"); diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index 2208b440..c209dd18 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -3,7 +3,7 @@ const _ = require("lodash"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); const { DimseQueryBuilder } = require("./queryBuilder"); const { JsStudyQueryTask } = require("./studyQueryTask"); -const { SeriesModel } = require("@dbModels/dicomSeries"); +const { SeriesModel } = require("@dbModels/series.model"); const { SeriesQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTask"); const { Attributes } = require("@dcm4che/data/Attributes"); const { createSeriesQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTaskInject"); diff --git a/models/mongodb/models/dicomSeries.js b/models/mongodb/models/series.model.js similarity index 100% rename from models/mongodb/models/dicomSeries.js rename to models/mongodb/models/series.model.js From 1499abbe74f1b10f83d8ed384eb926c846a87b33 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:26:21 +0800 Subject: [PATCH 115/365] refactor: rename `dicomToJpegTask.js` to `dicomToJpegTask.model.js` --- .../controller/STOW-RS/service/dicom-jpeg-generator.js | 2 +- .../models/{dicomToJpegTask.js => dicomToJpegTask.model.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename models/mongodb/models/{dicomToJpegTask.js => dicomToJpegTask.model.js} (100%) diff --git a/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js index 3d7e38df..55215360 100644 --- a/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js +++ b/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js @@ -3,7 +3,7 @@ const { Dcm2JpgExecutor } = require("@models/DICOM/dcm4che/wrapper/org/github/ch const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("@models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); const notImageSOPClass = require("@models/DICOM/dicomWEB/notImageSOPClass"); const { logger } = require("@root/utils/logs/log"); -const { DicomToJpegTaskModel } = require("@dbModels/dicomToJpegTask.js"); +const { DicomToJpegTaskModel } = require("@dbModels/dicomToJpegTask.model.js"); const colorette = require("colorette"); /** diff --git a/models/mongodb/models/dicomToJpegTask.js b/models/mongodb/models/dicomToJpegTask.model.js similarity index 100% rename from models/mongodb/models/dicomToJpegTask.js rename to models/mongodb/models/dicomToJpegTask.model.js From fd4fbd92cc10a106fa1439e92d2e69f87d2ec0c2 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:27:49 +0800 Subject: [PATCH 116/365] refactor: rename `dicomBulkData.js` to `dicomBulkData.model.js` --- api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js | 3 +-- models/DICOM/dicom-json-model.js | 2 +- .../models/{dicomBulkData.js => dicomBulkData.model.js} | 0 3 files changed, 2 insertions(+), 3 deletions(-) rename models/mongodb/models/{dicomBulkData.js => dicomBulkData.model.js} (100%) diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index 32bf3580..48d7dc14 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -1,7 +1,6 @@ const fs = require("fs"); const path = require("path"); -const dicomBulkDataModel = require("../../../../../../models/mongodb/models/dicomBulkData"); -const dicomModel = require("../../../../../../models/mongodb/models/dicom"); +const dicomBulkDataModel = require("@dbModels/dicomBulkData.model"); const { MultipartWriter } = require("../../../../../../utils/multipartWriter"); const { streamToBuffer } = require("@jorgeferrero/stream-to-buffer"); const { raccoonConfig } = require("../../../../../../config-class"); diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 01990241..a0ee5908 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -9,7 +9,7 @@ const { } = require("./dcmtk"); const flat = require("flat"); const shortHash = require("shorthash2"); -const dicomBulkDataModel = require("../mongodb/models/dicomBulkData"); +const dicomBulkDataModel = require("@dbModels/dicomBulkData.model"); const { logger } = require("../../utils/logs/log"); const { PatientModel } = require("@dbModels/patient.model"); const { tagsNeedStore } = require("./dicom-tags-mapping"); diff --git a/models/mongodb/models/dicomBulkData.js b/models/mongodb/models/dicomBulkData.model.js similarity index 100% rename from models/mongodb/models/dicomBulkData.js rename to models/mongodb/models/dicomBulkData.model.js From 3fe3ed4a37c387486575b48f5d4669b11d76f460 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:30:15 +0800 Subject: [PATCH 117/365] fix: can not load .model.js - split with .js --- models/mongodb/connector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/mongodb/connector.js b/models/mongodb/connector.js index aeea0b96..9383af28 100644 --- a/models/mongodb/connector.js +++ b/models/mongodb/connector.js @@ -85,7 +85,7 @@ function getCollections(dirname, collectionObj) { file.slice(-3) === ".js" ); for (let file of jsFilesInDir) { - const moduleName = file.split(".")[0]; + const moduleName = file.split(".js")[0]; console.log("moduleName :: ", moduleName); console.log("path : ", __dirname + dirname); collectionObj[moduleName] = require(__dirname + From f04fd9b4cc109a49f90f8f6f4866b26b721f1505 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 00:35:00 +0800 Subject: [PATCH 118/365] test: update models require location --- test/QIDO-RS-Service/common.test.js | 2 +- test/QIDO-RS-Service/patient.test.js | 2 +- test/patient.mongo.test.js | 2 +- test/query.test.js | 8 ++++---- test/store-instances.test.js | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/QIDO-RS-Service/common.test.js b/test/QIDO-RS-Service/common.test.js index 5766a711..3f7e9888 100644 --- a/test/QIDO-RS-Service/common.test.js +++ b/test/QIDO-RS-Service/common.test.js @@ -1,5 +1,5 @@ const mongoose = require("mongoose"); -const patientModel = require("../../models/mongodb/models/patient"); +const patientModel = require("../../models/mongodb/models/patient.model"); const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); diff --git a/test/QIDO-RS-Service/patient.test.js b/test/QIDO-RS-Service/patient.test.js index 37180fb4..d2fc319e 100644 --- a/test/QIDO-RS-Service/patient.test.js +++ b/test/QIDO-RS-Service/patient.test.js @@ -1,5 +1,5 @@ const mongoose = require("mongoose"); -const patientModel = require("../../models/mongodb/models/patient"); +const patientModel = require("../../models/mongodb/models/patient.model"); const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); diff --git a/test/patient.mongo.test.js b/test/patient.mongo.test.js index fff93fdd..5e040e44 100644 --- a/test/patient.mongo.test.js +++ b/test/patient.mongo.test.js @@ -1,5 +1,5 @@ const mongoose = require("mongoose"); -const patientModel = require("../models/mongodb/models/patient"); +const patientModel = require("../models/mongodb/models/patient.model"); const { DicomJsonModel } = require("../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); diff --git a/test/query.test.js b/test/query.test.js index f38a4a2e..062a89fd 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -4,10 +4,10 @@ const glob = require("glob"); const path = require("path"); const fsP = require("fs/promises"); const { StowRsService } = require("../api/dicom-web/controller/STOW-RS/service/stow-rs.service"); -const patientModel = require("../models/mongodb/models/patient"); -const dicomStudyModel = require("../models/mongodb/models/dicomStudy"); -const dicomSeriesModel = require("../models/mongodb/models/dicomSeries"); -const dicomModel = require("../models/mongodb/models/dicom"); +const patientModel = require("../models/mongodb/models/patient.model"); +const dicomStudyModel = require("../models/mongodb/models/study.model"); +const dicomSeriesModel = require("../models/mongodb/models/series.model"); +const dicomModel = require("../models/mongodb/models/instance.model"); const { expect } = require("chai"); const { convertAllQueryToDICOMTag } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); diff --git a/test/store-instances.test.js b/test/store-instances.test.js index 809799cd..e72c206e 100644 --- a/test/store-instances.test.js +++ b/test/store-instances.test.js @@ -4,10 +4,10 @@ const glob = require("glob"); const path = require("path"); const fsP = require("fs/promises"); const { StowRsService } = require("../api/dicom-web/controller/STOW-RS/service/stow-rs.service"); -const patientModel = require("../models/mongodb/models/patient"); -const dicomStudyModel = require("../models/mongodb/models/dicomStudy"); -const dicomSeriesModel = require("../models/mongodb/models/dicomSeries"); -const dicomModel = require("../models/mongodb/models/dicom"); +const patientModel = require("../models/mongodb/models/patient.model"); +const dicomStudyModel = require("../models/mongodb/models/study.model"); +const dicomSeriesModel = require("../models/mongodb/models/series.model"); +const dicomModel = require("../models/mongodb/models/instance.model"); const { expect } = require("chai"); async function storeDicomInstancesAndGet4Patients() { From acb470f0cc35279b11f3e0a68fff87e7505cc475 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 01:31:11 +0800 Subject: [PATCH 119/365] refactor: add `ApiErrorArrayHandler` to handler response of custom errors --- api/WADO-URI/controller/retrieveInstance.js | 2 +- api/WADO-URI/service/WADO-URI.service.js | 59 ++-------- .../controller/QIDO-RS/base.controller.js | 5 +- .../controller/WADO-RS/base.controller.js | 5 +- .../WADO-RS/bulkdata/base.controller.js | 5 +- .../controller/WADO-RS/bulkdata/bulkdata.js | 5 +- .../WADO-RS/deletion/base.controller.js | 12 +- .../WADO-RS/metadata/base.controller.js | 5 +- .../WADO-RS/rendered/base.controller.js | 5 +- error/api-errors.handler.js | 106 ++++++++++++++++++ error/controller.handler.js | 24 ---- 11 files changed, 135 insertions(+), 98 deletions(-) create mode 100644 error/api-errors.handler.js delete mode 100644 error/controller.handler.js diff --git a/api/WADO-URI/controller/retrieveInstance.js b/api/WADO-URI/controller/retrieveInstance.js index f80342f3..806168e8 100644 --- a/api/WADO-URI/controller/retrieveInstance.js +++ b/api/WADO-URI/controller/retrieveInstance.js @@ -5,8 +5,8 @@ const { ApiLogger } = require("../../../utils/logs/api-logger"); class RetrieveSingleInstanceController extends Controller { constructor(req, res) { super(req, res); - this.service = new WadoUriService(req, res); this.logger = new ApiLogger(this.request, "WADO-URI"); + this.service = new WadoUriService(req, res, this.logger); } async mainProcess() { diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 77c71940..36365fa5 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -12,6 +12,7 @@ const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { DicomWebService } = require("@root/api/dicom-web/service/dicom-web.service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class WadoUriService { @@ -20,9 +21,10 @@ class WadoUriService { * @param {import("http").IncomingMessage} req * @param {import("http").ServerResponse} res */ - constructor(req, res) { + constructor(req, res, apiLogger) { this.request = req; this.response = res; + this.apiLogger = apiLogger; this.auditBeginTransferring(); } @@ -38,24 +40,8 @@ class WadoUriService { } catch (e) { this.auditInstanceTransferred(EventOutcomeIndicator.MajorFailure); - if (e instanceof NotFoundInstanceError) { - this.response.writeHead(404, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - } else if (e instanceof InstanceGoneError) { - this.response.writeHead(410, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify({ - Details: e.message, - HttpStatus: 410, - Message: "Image Gone", - Method: "GET" - })); - } - - throw e; + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } @@ -72,39 +58,8 @@ class WadoUriService { } catch (e) { this.auditInstanceTransferred(EventOutcomeIndicator.MajorFailure); - if (e instanceof NotFoundInstanceError) { - - this.response.writeHead(404, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - - } else if (e instanceof InvalidFrameNumberError) { - - this.response.writeHead(400, { - "Content-Type": "application/dicom+json" - }); - - return this.response.end(JSON.stringify({ - Details: e.message, - HttpStatus: 400, - Message: "Bad request", - Method: "GET" - })); - - } else if (e instanceof InstanceGoneError) { - this.response.writeHead(410, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify({ - Details: e.message, - HttpStatus: 410, - Message: "Image Gone", - Method: "GET" - })); - } - - throw e; + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/QIDO-RS/base.controller.js b/api/dicom-web/controller/QIDO-RS/base.controller.js index d9a2f162..d94d85e1 100644 --- a/api/dicom-web/controller/QIDO-RS/base.controller.js +++ b/api/dicom-web/controller/QIDO-RS/base.controller.js @@ -1,7 +1,7 @@ -const { ControllerErrorHandler } = require("@error/controller.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { QidoRsService } = require("./service/QIDO-RS.service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseQueryController extends Controller { constructor(req, res) { @@ -22,7 +22,8 @@ class BaseQueryController extends Controller { let qidoRsService = new QidoRsService(this.request, this.response, this.level); await qidoRsService.getAndResponseDicomJson(); } catch (e) { - return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/WADO-RS/base.controller.js b/api/dicom-web/controller/WADO-RS/base.controller.js index 21658f46..7636a755 100644 --- a/api/dicom-web/controller/WADO-RS/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/base.controller.js @@ -4,7 +4,7 @@ const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { WADOZip } = require("./service/WADOZip"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { sendNotSupportedMediaType, getAcceptType, supportInstanceMultipartType, ImageMultipartWriter, InstanceImagePathFactory, multipartContentTypeWriter, StudyImagePathFactory, SeriesImagePathFactory } = require("./service/WADO-RS.service"); -const { ControllerErrorHandler } = require("@error/controller.handler"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseRetrieveController extends Controller { constructor(req, res) { @@ -32,7 +32,8 @@ class BaseRetrieveController extends Controller { return sendNotSupportedMediaType(this.response, this.request.headers.accept); } catch (e) { - return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js index 646ba1cd..7163a655 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js @@ -2,7 +2,7 @@ const { Controller } = require("@root/api/controller.class"); const { StudyBulkDataFactory, BulkDataService } = require("./service/bulkdata"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { StudyImagePathFactory } = require("../service/WADO-RS.service"); -const { ControllerErrorHandler } = require("@error/controller.handler"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseBulkDataController extends Controller { constructor(req, res) { @@ -40,7 +40,8 @@ class BaseBulkDataController extends Controller { bulkDataService.multipartWriter.writeFinalBoundary(); return this.response.end(); } catch(e) { - return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js index a199fa29..9b4ba8a1 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js @@ -5,7 +5,7 @@ const { BulkDataService, SpecificBulkDataFactory } = require("./service/bulkdata const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); const { BaseBulkDataController } = require("./base.controller"); const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -const { ControllerErrorHandler } = require("@error/controller.handler"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BulkDataController extends BaseBulkDataController { constructor(req, res) { @@ -31,7 +31,8 @@ class BulkDataController extends BaseBulkDataController { bulkDataService.multipartWriter.writeFinalBoundary(); return this.response.end(); } catch(e) { - return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js index d65ec5d3..5d455f6f 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js @@ -3,7 +3,7 @@ const { ApiLogger } = require("@root/utils/logs/api-logger"); const { DeleteService } = require("./service/delete"); const { NotFoundInstanceError } = require("@error/dicom-instance"); const { getNotFoundErrorMessage, getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); -const { ControllerErrorHandler } = require("@error/controller.handler"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseDeleteController extends Controller { constructor(req, res) { @@ -26,14 +26,8 @@ class BaseDeleteController extends Controller { Method: "DELETE" }); } catch(e) { - - if (e instanceof NotFoundInstanceError) { - return this.response.status(404).json( - getNotFoundErrorMessage(e.message) - ); - } - - return ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js index fcacad84..9c91e8a8 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js @@ -2,7 +2,7 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { StudyImagePathFactory } = require("../service/WADO-RS.service"); const { MetadataService } = require("../service/metadata.service"); -const { ControllerErrorHandler } = require("@error/controller.handler"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseRetrieveMetadataController extends Controller { constructor(req, res) { @@ -33,7 +33,8 @@ class BaseRetrieveMetadataController extends Controller { return this.response.end(); } catch (e) { - ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js index d0b31825..5594de04 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js @@ -6,7 +6,7 @@ const { const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { Controller } = require("../../../../controller.class"); -const { ControllerErrorHandler } = require("@error/controller.handler"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseRetrieveRenderedController extends Controller { /** @@ -61,7 +61,8 @@ class BaseRetrieveRenderedController extends Controller { return this.response.end(); } catch(e) { - ControllerErrorHandler.raiseInternalServerError(e, this.apiLogger, this.response); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/error/api-errors.handler.js b/error/api-errors.handler.js new file mode 100644 index 00000000..eba07b06 --- /dev/null +++ b/error/api-errors.handler.js @@ -0,0 +1,106 @@ +const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); + + +class DicomWebServiceErrorHandler { + + static doErrorResponse(response, e) { + return response.status(e.code).json({ + status: e.status, + message: e.message + }); + } +} + +class NotFoundInstanceErrorHandler { + static doErrorResponse(response, e) { + response.writeHead(404, { + "Content-Type": "application/dicom+json" + }); + return response.end(JSON.stringify(getNotFoundErrorMessage(e.message))); + } +} + +class InstanceGoneErrorHandler { + static doErrorResponse(response, e) { + response.writeHead(410, { + "Content-Type": "application/dicom+json" + }); + return response.end(JSON.stringify({ + Details: e.message, + HttpStatus: 410, + Message: "Image Gone", + Method: "GET" + })); + } +} + +class InvalidFrameNumberErrorHandler { + static doErrorResponse(response, e) { + response.writeHead(400, { + "Content-Type": "application/dicom+json" + }); + + return response.end(JSON.stringify({ + Details: e.message, + HttpStatus: 400, + Message: "Bad request", + Method: "GET" + })); + } +} + +const ApiErrorHandlerMapping = { + "DicomWebServiceError": DicomWebServiceErrorHandler, + "NotFoundInstanceError": NotFoundInstanceErrorHandler, + "InstanceGoneError": InstanceGoneErrorHandler, + "InvalidFrameNumberError": InvalidFrameNumberErrorHandler +}; + +class ApiErrorArrayHandler { + /** + * @param {import("express").Response} res + * @param {ApiLogger} apiLogger + * @param {Error} e + */ + constructor(res, apiLogger, e) { + this.response = res; + /** @type {ApiLogger} */ + this.apiLogger = apiLogger; + /** @type {Error} */ + this.error = e; + } + + doErrorResponse() { + if (this.isCustomError(this.error)) { + this.apiLogger.logger.error(this.error); + return ApiErrorHandlerMapping[this.error.name].doErrorResponse(this.response, this.error); + } else { + ApiErrorArrayHandler.raiseInternalServerError(this.error, this.response, this.apiLogger); + } + } + + /** + * + * @param {import("express").Response} response + * @param {ApiLogger} apiLogger + * @param {Error} e + */ + static raiseInternalServerError(response, apiLogger, e) { + apiLogger.logger.error(e); + + if (!response.headersSent) { + response.writeHead(500, { + "Content-Type": "application/dicom+json" + }); + return response.json(getInternalServerErrorMessage("An exception occurred")); + } + return response.end(); + } + + isCustomError(e) { + return Object.keys(ApiErrorHandlerMapping).find(key => e.name === key); + } +} + +module.exports.ApiErrorArrayHandler = ApiErrorArrayHandler; \ No newline at end of file diff --git a/error/controller.handler.js b/error/controller.handler.js deleted file mode 100644 index 364444eb..00000000 --- a/error/controller.handler.js +++ /dev/null @@ -1,24 +0,0 @@ -const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); - -class ControllerErrorHandler { - - /** - * - * @param {Error} e - * @param {ApiLogger} apiLogger - * @param {import("express").Response} response - */ - static raiseInternalServerError(e, apiLogger, response) { - apiLogger.logger.error(e); - - if (!response.headersSent) { - response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return response.json(getInternalServerErrorMessage("An exception occurred")); - } - return response.end(); - } -} - -module.exports.ControllerErrorHandler = ControllerErrorHandler; \ No newline at end of file From 9392d66b1cc93a6dbdfbe1de723f5b1c9a3acc71 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 15:46:04 +0800 Subject: [PATCH 120/365] refactor: duplicate codes of `findOneWorkItem` --- .../UPS-RS/service/base-workItem.service.js | 29 +++++++++++++++++-- .../UPS-RS/service/cancel.service.js | 23 --------------- .../service/change-workItem-state.service.js | 22 ++------------ .../UPS-RS/service/get-workItem.service.js | 4 +-- .../UPS-RS/service/subscribe.service.js | 24 --------------- .../UPS-RS/service/unsubscribe.service.js | 23 --------------- .../UPS-RS/service/update-workItem.service.js | 19 +----------- 7 files changed, 32 insertions(+), 112 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 8c60c0b9..ec356ad5 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -3,11 +3,12 @@ const { WorkItemEvent } = require("./workItem-event"); const { DicomJsonModel } = require("@dicom-json-model"); const { findWsArrayByAeTitle } = require("@root/websocket"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); -const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/QIDO-RS.service"); +const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); -const workItemModel = require("@models/mongodb/models/workItems"); +const workItemModel = require("@dbModels/workItems"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); class BaseWorkItemService { constructor(req, res) { @@ -159,6 +160,30 @@ class BaseWorkItemService { } return eventInformation; } + + /** + * + * @param {string} upsInstanceUID + * @returns + */ + async findOneWorkItem(upsInstanceUID, toObject=false) { + let workItem = await workItemModel.findOne({ + upsInstanceUID: upsInstanceUID + }); + + if (!workItem) { + throw new DicomWebServiceError( + DicomWebStatusCodes.UPSDoesNotExist, + "The UPS instance not exist", + 404 + ); + } + if (toObject) { + workItem = workItem.toObject(); + } + + return new DicomJsonModel(workItem); + } } module.exports.BaseWorkItemService = BaseWorkItemService; \ No newline at end of file diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index 9d9c04b7..08122b32 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -58,29 +58,6 @@ class CancelWorkItemService extends BaseWorkItemService { } - /** - * - * @param {string} upsInstanceUID - * @returns - */ - async findOneWorkItem(upsInstanceUID) { - - let workItem = await workItemModel.findOne({ - upsInstanceUID: upsInstanceUID - }); - - if (!workItem) { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - - return new DicomJsonModel(workItem); - - } - async addCancelEvent() { let hitSubscriptions = await this.getHitSubscriptions(this.workItem); let aeTitles = hitSubscriptions.map(v => v.aeTitle); diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index f1cef508..44e7f6ca 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -36,8 +36,8 @@ class ChangeWorkItemStateService extends BaseWorkItemService { this.workItemTransactionUID = ""; } - async changeWorkItemState() { - await this.findOneWorkItem(); + async changeWorkItemState() { + this.workItem = await this.findOneWorkItem(this.request.params.workItem); this.workItemState = this.workItem.getString("00741000"); this.workItemTransactionUID = this.workItem.getString("00081195"); @@ -71,24 +71,6 @@ class ChangeWorkItemStateService extends BaseWorkItemService { this.triggerUpsEvents(); } - async findOneWorkItem() { - - let workItem = await workItemModel.findOne({ - upsInstanceUID: this.request.params.workItem - }); - - if (!workItem) { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - - this.workItem = new DicomJsonModel(workItem); - - } - inProgressChange() { if (this.workItemState === "IN PROGRESS") { throw new DicomWebServiceError( diff --git a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 97147800..9b9f88d5 100644 --- a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -1,9 +1,9 @@ const _ = require("lodash"); const workItemsModel = require("@models/mongodb/models/workItems"); const { - convertAllQueryToDICOMTag, convertRequestQueryToMongoQuery -} = require("../../QIDO-RS/service/QIDO-RS.service"); +} = require("../../QIDO-RS/service/query-dicom-json-factory"); +const { convertAllQueryToDICOMTag } = require("../../QIDO-RS/service/QIDO-RS.service"); class GetWorkItemService { constructor(req, res) { diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index ece30b23..a71eae9b 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -45,30 +45,6 @@ class SubscribeService extends BaseWorkItemService { await this.triggerUpsEvents(); } - - /** - * - * @param {string} upsInstanceUID - * @returns - */ - async findOneWorkItem(upsInstanceUID) { - - let workItem = await workItemModel.findOne({ - upsInstanceUID: upsInstanceUID - }); - - if (!workItem) { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - - return new DicomJsonModel(workItem); - - } - //#region Subscription async findOneSubscription() { diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 3848463f..62fac381 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -57,29 +57,6 @@ class UnSubscribeService extends BaseWorkItemService { } - /** - * - * @param {string} upsInstanceUID - * @returns {Promise} - */ - async findOneWorkItem(upsInstanceUID) { - - let workItem = await workItemModel.findOne({ - upsInstanceUID: upsInstanceUID - }); - - if (!workItem) { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - - return new DicomJsonModel(workItem); - - } - /** * * @param {DicomJsonModel} workItem diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index cfc9810e..b1b33d8e 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -45,7 +45,7 @@ class UpdateWorkItemService extends BaseWorkItemService { async updateUps() { this.transactionUID = this.requestWorkItem.getString("00081195"); - await this.findOneWorkItem(); + this.workItem = await this.findOneWorkItem(this.request.params.workItem, true); await this.checkRequestUpsIsValid(); this.adjustRequestWorkItem(); @@ -111,23 +111,6 @@ class UpdateWorkItemService extends BaseWorkItemService { } } - async findOneWorkItem() { - - let workItem = await workItemModel.findOne({ - upsInstanceUID: this.request.params.workItem - }); - - if (!workItem) { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - - this.workItem = new DicomJsonModel(workItem.toObject()); - - } checkRequestUpsIsValid() { let procedureState = this.workItem.getString("00741000"); From e889b0c239457c1a88171a553a43f8697ed6c42b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 11 Nov 2023 16:30:14 +0800 Subject: [PATCH 121/365] refactor: duplicate codes for bulkdata api - response Bulk Data Array and response single bulk data --- .../WADO-RS/bulkdata/base.controller.js | 43 ++++++++++++------- .../controller/WADO-RS/bulkdata/bulkdata.js | 16 ------- .../WADO-RS/bulkdata/service/bulkdata.js | 2 +- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js index 7163a655..cfb4dbdc 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js @@ -11,6 +11,7 @@ class BaseBulkDataController extends Controller { this.apiLogger.addTokenValue(); this.bulkDataFactoryType = StudyBulkDataFactory; this.imagePathFactoryType = StudyImagePathFactory; + this.bulkDataService = new BulkDataService(this.request, this.response, this.bulkDataFactoryType); } logAction() { @@ -20,30 +21,42 @@ class BaseBulkDataController extends Controller { async mainProcess() { this.logAction(); - let bulkDataService = new BulkDataService(this.request, this.response, this.bulkDataFactoryType); - try { - let bulkDataArray = await bulkDataService.getBulkData(); - for(let bulkData of bulkDataArray) { - await bulkDataService.writeBulkData(bulkData); - } + this.logAction(); - let imagePathFactory = new this.imagePathFactoryType({ - ...this.request.params - }); - await imagePathFactory.getImagePaths(); - - for(let imagePathObj of imagePathFactory.imagePaths) { - await bulkDataService.writeBulkData(imagePathObj); + let bulkData = await this.bulkDataService.getBulkData(); + if (Array.isArray(bulkData)) { + await this.responseBulkDataArray(bulkData); + } else { + await this.responseBulkData(bulkData); } - bulkDataService.multipartWriter.writeFinalBoundary(); + this.bulkDataService.multipartWriter.writeFinalBoundary(); return this.response.end(); - } catch(e) { + } catch (e) { let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); return apiErrorArrayHandler.doErrorResponse(); } } + + async responseBulkDataArray(bulkDataArray) { + for (let bulkData of bulkDataArray) { + await this.bulkDataService.writeBulkData(bulkData); + } + + let imagePathFactory = new this.imagePathFactoryType({ + ...this.request.params + }); + await imagePathFactory.getImagePaths(); + + for (let imagePathObj of imagePathFactory.imagePaths) { + await this.bulkDataService.writeBulkData(imagePathObj); + } + } + + async responseBulkData(bulkData) { + await this.bulkDataService.writeBulkData(bulkData); + } } module.exports.BaseBulkDataController = BaseBulkDataController; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js index 9b4ba8a1..419c5297 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js @@ -20,22 +20,6 @@ class BulkDataController extends BaseBulkDataController { , SOPInstanceUID: ${this.request.params.instanceUID}`); } - async mainProcess() { - this.logAction(); - - let bulkDataService = new BulkDataService(this.request, this.response, this.bulkDataFactoryType); - - try { - let bulkData = await bulkDataService.getBulkData(); - await bulkDataService.writeBulkData(bulkData); - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch(e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); - return apiErrorArrayHandler.doErrorResponse(); - } - - } } diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index 48d7dc14..efbe1e81 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -51,7 +51,7 @@ class BulkDataService { async getBulkData() { return await this.bulkDataFactory.getBulkData(); } - + } class BulkDataFactory { From edbf7a6b1b4e641a6283711646f92b55888c9eed Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 12 Nov 2023 01:05:03 +0800 Subject: [PATCH 122/365] refactor: using `ApiErrorArrayHandler` in controllers --- api/WADO-URI/controller/retrieveInstance.js | 13 ++------- api/dicom-web/controller/UPS-RS/cancel.js | 26 ++++------------- .../UPS-RS/change-workItem-state.js | 26 ++++------------- .../controller/UPS-RS/create-workItems.js | 28 +++++-------------- .../controller/UPS-RS/get-workItem.js | 19 ++++--------- api/dicom-web/controller/UPS-RS/subscribe.js | 26 ++++------------- .../controller/UPS-RS/suspend-subscription.js | 26 ++++------------- .../controller/UPS-RS/unsubscribe.js | 26 ++++------------- .../controller/UPS-RS/update-workItem.js | 26 ++++------------- 9 files changed, 52 insertions(+), 164 deletions(-) diff --git a/api/WADO-URI/controller/retrieveInstance.js b/api/WADO-URI/controller/retrieveInstance.js index 806168e8..efb12700 100644 --- a/api/WADO-URI/controller/retrieveInstance.js +++ b/api/WADO-URI/controller/retrieveInstance.js @@ -1,6 +1,7 @@ const { WadoUriService, NotFoundInstanceError } = require("../service/WADO-URI.service"); const { Controller } = require("../../controller.class"); const { ApiLogger } = require("../../../utils/logs/api-logger"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class RetrieveSingleInstanceController extends Controller { constructor(req, res) { @@ -27,16 +28,8 @@ class RetrieveSingleInstanceController extends Controller { } } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - this.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.logger, e); + return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/UPS-RS/cancel.js b/api/dicom-web/controller/UPS-RS/cancel.js index a58ac60c..d8a6bbc4 100644 --- a/api/dicom-web/controller/UPS-RS/cancel.js +++ b/api/dicom-web/controller/UPS-RS/cancel.js @@ -10,17 +10,18 @@ const { const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class CancelWorkItemController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info(`Cancel Work Item, params: ${this.paramsToString()}`); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info(`Cancel Work Item, params: ${this.paramsToString()}`); try { let service = new CancelWorkItemService(this.request, this.response); @@ -31,23 +32,8 @@ class CancelWorkItemController extends Controller { .status(202) .end(); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - if (e instanceof DicomWebServiceError) { - return this.response.status(e.code).json({ - status: e.status, - message: e.message - }); - } - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "An Server Exception Occurred" - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/UPS-RS/change-workItem-state.js b/api/dicom-web/controller/UPS-RS/change-workItem-state.js index 17c14915..05593e71 100644 --- a/api/dicom-web/controller/UPS-RS/change-workItem-state.js +++ b/api/dicom-web/controller/UPS-RS/change-workItem-state.js @@ -4,17 +4,18 @@ const { const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class ChangeWorkItemStateController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info(`Update workItem, params: ${this.paramsToString()}`); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info(`Update workItem, params: ${this.paramsToString()}`); try { let service = new ChangeWorkItemStateService(this.request, this.response); @@ -24,23 +25,8 @@ class ChangeWorkItemStateController extends Controller { .status(200) .end(); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - if (e instanceof DicomWebServiceError) { - return this.response.status(e.code).json({ - status: e.status, - message: e.message - }); - } - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "An Server Exception Occurred" - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/UPS-RS/create-workItems.js b/api/dicom-web/controller/UPS-RS/create-workItems.js index 41091426..e99a3f3c 100644 --- a/api/dicom-web/controller/UPS-RS/create-workItems.js +++ b/api/dicom-web/controller/UPS-RS/create-workItems.js @@ -5,41 +5,27 @@ const { const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class CreateWorkItemController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info("Create workItem"); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info("Create workItem"); try { let workItemService = new CreateWorkItemService(this.request, this.response); let workItem = await workItemService.createUps(); - apiLogger.logger.info(`Create workItem ${workItem.upsInstanceUID} successful`); + this.apiLogger.logger.info(`Create workItem ${workItem.upsInstanceUID} successful`); return this.response.status(201).send(); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - if (e instanceof DicomWebServiceError) { - return this.response.status(e.code).send({ - status: e.status, - message: e.message - }); - } - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "An Server Exception Occurred" - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/UPS-RS/get-workItem.js b/api/dicom-web/controller/UPS-RS/get-workItem.js index 1ef3ead6..17e66fde 100644 --- a/api/dicom-web/controller/UPS-RS/get-workItem.js +++ b/api/dicom-web/controller/UPS-RS/get-workItem.js @@ -4,17 +4,18 @@ const { } = require("./service/get-workItem.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class GetWorkItemController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info(`Get workItem, query: ${this.queryToString()}, param: ${this.paramsToString()}`); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info(`Get workItem, query: ${this.queryToString()}, param: ${this.paramsToString()}`); try { let getWorkItemService = new GetWorkItemService(this.request, this.response); @@ -28,16 +29,8 @@ class GetWorkItemController extends Controller { return this.response.set("Content-Type", "application/dicom+json").status(200).json(workItems); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/UPS-RS/subscribe.js b/api/dicom-web/controller/UPS-RS/subscribe.js index 33b20a46..4ad936c1 100644 --- a/api/dicom-web/controller/UPS-RS/subscribe.js +++ b/api/dicom-web/controller/UPS-RS/subscribe.js @@ -4,17 +4,18 @@ const { const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class SubscribeWorkItemController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info(`Create Subscription, params: ${this.paramsToString()}`); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info(`Create Subscription, params: ${this.paramsToString()}`); try { let service = new SubscribeService(this.request, this.response); @@ -25,23 +26,8 @@ class SubscribeWorkItemController extends Controller { .status(201) .end(); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - if (e instanceof DicomWebServiceError) { - return this.response.status(e.code).json({ - status: e.status, - message: e.message - }); - } - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "An Server Exception Occurred" - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/UPS-RS/suspend-subscription.js b/api/dicom-web/controller/UPS-RS/suspend-subscription.js index 0115b810..8161f525 100644 --- a/api/dicom-web/controller/UPS-RS/suspend-subscription.js +++ b/api/dicom-web/controller/UPS-RS/suspend-subscription.js @@ -10,17 +10,18 @@ const { const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class SuspendSubscribeWorkItemController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info(`Suspend Subscription, params: ${this.paramsToString()}`); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info(`Suspend Subscription, params: ${this.paramsToString()}`); try { let service = new SuspendSubscribeService(this.request, this.response); @@ -31,23 +32,8 @@ class SuspendSubscribeWorkItemController extends Controller { .status(200) .end(); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - if (e instanceof DicomWebServiceError) { - return this.response.status(e.code).json({ - status: e.status, - message: e.message - }); - } - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "An Server Exception Occurred" - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/UPS-RS/unsubscribe.js b/api/dicom-web/controller/UPS-RS/unsubscribe.js index 2fae701d..e2cf36be 100644 --- a/api/dicom-web/controller/UPS-RS/unsubscribe.js +++ b/api/dicom-web/controller/UPS-RS/unsubscribe.js @@ -4,17 +4,18 @@ const { const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class UnSubscribeWorkItemController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info(`UnSubscription, params: ${this.paramsToString()}`); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info(`UnSubscription, params: ${this.paramsToString()}`); try { let service = new UnSubscribeService(this.request, this.response); @@ -25,23 +26,8 @@ class UnSubscribeWorkItemController extends Controller { .status(200) .end(); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - if (e instanceof DicomWebServiceError) { - return this.response.status(e.code).json({ - status: e.status, - message: e.message - }); - } - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "An Server Exception Occurred" - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } diff --git a/api/dicom-web/controller/UPS-RS/update-workItem.js b/api/dicom-web/controller/UPS-RS/update-workItem.js index 466636d6..f1c77d2b 100644 --- a/api/dicom-web/controller/UPS-RS/update-workItem.js +++ b/api/dicom-web/controller/UPS-RS/update-workItem.js @@ -4,17 +4,18 @@ const { const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class UpdateWorkItemController extends Controller { constructor(req, res) { super(req, res); + this.apiLogger = new ApiLogger(this.request, "UPS-RS"); } async mainProcess() { - let apiLogger = new ApiLogger(this.request, "UPS-RS"); - apiLogger.addTokenValue(); - apiLogger.logger.info(`Update workItem, params: ${this.paramsToString()}`); + this.apiLogger.addTokenValue(); + this.apiLogger.logger.info(`Update workItem, params: ${this.paramsToString()}`); try { let service = new UpdateWorkItemService(this.request, this.response); @@ -25,23 +26,8 @@ class UpdateWorkItemController extends Controller { .status(200) .end(); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - if (e instanceof DicomWebServiceError) { - return this.response.status(e.code).json({ - status: e.status, - message: e.message - }); - } - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "An Server Exception Occurred" - })); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } From c844a1a136bf6bf993455d84956211a79c76112a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 12 Nov 2023 13:22:38 +0800 Subject: [PATCH 123/365] refactor: extract class for patient inject proxies --- dimse/patientQueryTask.js | 114 +++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 38 deletions(-) diff --git a/dimse/patientQueryTask.js b/dimse/patientQueryTask.js index 2af75893..e6cb70ce 100644 --- a/dimse/patientQueryTask.js +++ b/dimse/patientQueryTask.js @@ -43,56 +43,26 @@ class JsPatientQueryTask { ); await this.initCursor(); - await this.patientQueryTaskInjectMethods.wrappedFindNextPatient(); + await this.patientQueryTaskProxy.wrappedFindNextPatient(); return patientQueryTask; } getQueryTaskInjectProxy() { - /** @type { QueryTaskInjectInterface } */ - this.patientBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.patientAttr); - }, - nextMatch: async () => { - let tempRecord = this.patientAttr; - await this.patientQueryTaskInjectMethods.wrappedFindNextPatient(); - return tempRecord; - }, - adjust: async (match) => { - return this.patientAdjust(match); - } - }; - - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.patientBasicQueryTaskInjectMethods); + // for creating one + if (!this.matchIteratorProxy) { + this.matchIteratorProxy = new PatientMatchIteratorProxy(this); } - return this.queryTaskInjectProxy; + return this.matchIteratorProxy.get(); } getPatientQueryTaskInjectProxy() { - - /** @type { PatientQueryTaskInjectInterface }*/ - this.patientQueryTaskInjectMethods = { - wrappedFindNextPatient: async () => { - await this.patientQueryTaskInjectMethods.findNextPatient(); - }, - getPatient: async () => { - this.patient = await this.cursor.next(); - this.patientAttr = this.patient ? await this.patient.getAttributes() : null; - }, - findNextPatient: async () => { - await this.patientQueryTaskInjectMethods.getPatient(); - return !_.isNull(this.patientAttr); - } - }; - + // for creating once if (!this.patientQueryTaskProxy) { - this.patientQueryTaskProxy = createPatientQueryTaskInjectProxy(this.patientQueryTaskInjectMethods); + this.patientQueryTaskProxy = new PatientQueryTaskInjectProxy(this); } - - return this.patientQueryTaskProxy; + return this.patientQueryTaskProxy.get(); } /** @@ -168,4 +138,72 @@ class JsPatientQueryTask { } } +class PatientQueryTaskInjectProxy { + /** + * + * @param {JsPatientQueryTask} queryTask + */ + constructor(patientQueryTask) { + /** @type {JsPatientQueryTask} */ + this.patientQueryTask = patientQueryTask; + } + + get() { + return createPatientQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + wrappedFindNextPatient: this.wrappedFindNextPatient.bind(this), + getPatient: this.getPatient.bind(this), + findNextPatient: this.findNextPatient.bind(this) + }; + } + + async wrappedFindNextPatient() { + await this.findNextPatient(); + } + + async findNextPatient() { + await this.getPatient(); + return !_.isNull(this.patientQueryTask.patientAttr); + } + + async getPatient() { + this.patientQueryTask.patient = await this.patientQueryTask.cursor.next(); + this.patientQueryTask.patientAttr = this.patientQueryTask.patient ? await this.patientQueryTask.patient.getAttributes() : null; + } +} + +class PatientMatchIteratorProxy { + constructor(patientQueryTask) { + /** @type {JsPatientQueryTask} */ + this.patientQueryTask = patientQueryTask; + } + + get() { + return createQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + hasMoreMatches: () => { + return !_.isNull(this.patientQueryTask.patientAttr); + }, + nextMatch: async () => { + let tempRecord = this.patientQueryTask.patientAttr; + await this.patientQueryTask.patientQueryTaskProxy.wrappedFindNextPatient(); + return tempRecord; + }, + adjust: async (match) => { + return this.patientQueryTask.patientAdjust(match); + } + }; + } +} + module.exports.JsPatientQueryTask = JsPatientQueryTask; \ No newline at end of file From 67e5878996d865d02deab089fb0e683ef8bba544 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 12 Nov 2023 13:47:39 +0800 Subject: [PATCH 124/365] refactor: add `QueryTaskUtils` for duplicate codes --- dimse/instanceQueryTask.js | 30 +++++++------------ dimse/seriesQueryTask.js | 30 +++++++------------ dimse/studyQueryTask.js | 30 +++++++------------ dimse/utils.js | 59 +++++++++++++++++++++++++++++++++++++- 4 files changed, 88 insertions(+), 61 deletions(-) diff --git a/dimse/instanceQueryTask.js b/dimse/instanceQueryTask.js index 996078b2..04647608 100644 --- a/dimse/instanceQueryTask.js +++ b/dimse/instanceQueryTask.js @@ -13,6 +13,7 @@ const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { UID } = require("@dcm4che/data/UID"); +const { QueryTaskUtils } = require("./utils"); class JsInstanceQueryTask extends JsSeriesQueryTask { @@ -114,31 +115,20 @@ class JsInstanceQueryTask extends JsSeriesQueryTask { } async getNextInstanceCursor() { - let queryAudit = new AuditManager( - EventType.QUERY, EventOutcomeIndicator.Success, - await this.as.getRemoteAET(), await this.as.getRemoteHostName(), - await this.as.getLocalAET(), await this.as.getLocalHostName() - ); - - let queryAttr = await Attributes.newInstanceAsync(); - await queryAttr.addAll(this.keys); - await queryAttr.addSelected(this.seriesAttr, [Tag.PatientID, Tag.StudyInstanceUID, Tag.SeriesInstanceUID]); - - let queryBuilder = new DimseQueryBuilder(queryAttr, "instance"); - let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - queryAudit.onQuery( + let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); + + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.seriesAttr); + let dbQuery = await QueryTaskUtils.getDbQuery(queryAttr, "instance"); + queryAuditManager.onQuery( UID.StudyRootQueryRetrieveInformationModelFind, - JSON.stringify(mongoQuery.$match), + JSON.stringify(dbQuery), "UTF-8" ); - let returnKeys = this.getReturnKeys(normalQuery); - - logger.info(`do DIMSE Instance query: ${JSON.stringify(mongoQuery.$match)}`); + logger.info(`do DIMSE Instance query: ${JSON.stringify(dbQuery)}`); this.instanceCursor = await InstanceModel.getDimseResultCursor({ - ...mongoQuery.$match - }, returnKeys); + ...dbQuery + }, await QueryTaskUtils.getReturnKeys(queryAttr, "instance")); } } diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index c209dd18..6d3b4255 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -13,6 +13,7 @@ const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { UID } = require("@dcm4che/data/UID"); +const { QueryTaskUtils } = require("./utils"); class JsSeriesQueryTask extends JsStudyQueryTask { constructor(as, pc, rq, keys) { @@ -112,31 +113,20 @@ class JsSeriesQueryTask extends JsStudyQueryTask { } async getNextSeriesCursor() { - let queryAudit = new AuditManager( - EventType.QUERY, EventOutcomeIndicator.Success, - await this.as.getRemoteAET(), await this.as.getRemoteHostName(), - await this.as.getLocalAET(), await this.as.getLocalHostName() - ); - - let queryAttr = await Attributes.newInstanceAsync(); - await queryAttr.addAll(this.keys); - await queryAttr.addSelected(this.studyAttr, [Tag.PatientID, Tag.StudyInstanceUID]); - - let queryBuilder = new DimseQueryBuilder(queryAttr, "series"); - let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - queryAudit.onQuery( + let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); + + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.studyAttr); + let dbQuery = await QueryTaskUtils.getDbQuery(queryAttr, "series"); + queryAuditManager.onQuery( UID.StudyRootQueryRetrieveInformationModelFind, - JSON.stringify(mongoQuery.$match), + JSON.stringify(dbQuery), "UTF-8" ); - let returnKeys = this.getReturnKeys(normalQuery); - - logger.info(`do DIMSE Series query: ${JSON.stringify(mongoQuery.$match)}`); + logger.info(`do DIMSE Series query: ${JSON.stringify(dbQuery)}`); this.seriesCursor = await SeriesModel.getDimseResultCursor({ - ...mongoQuery.$match - }, returnKeys); + ...dbQuery + }, await QueryTaskUtils.getReturnKeys(queryAttr, "series")); } } diff --git a/dimse/studyQueryTask.js b/dimse/studyQueryTask.js index a4e5e2b2..60d12afd 100644 --- a/dimse/studyQueryTask.js +++ b/dimse/studyQueryTask.js @@ -13,6 +13,7 @@ const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { UID } = require("@dcm4che/data/UID"); +const { QueryTaskUtils } = require("./utils"); class JsStudyQueryTask extends JsPatientQueryTask { constructor(as, pc, rq, keys) { @@ -93,7 +94,7 @@ class JsStudyQueryTask extends JsPatientQueryTask { await this.studyQueryTaskInjectMethods.getStudy(); } - while (!this.studyAttr && await this.patientQueryTaskInjectMethods.findNextPatient()) { + while (!this.studyAttr && await this.patientQueryTaskProxy.findNextPatient()) { await this.getNextStudyCursor(); await this.studyQueryTaskInjectMethods.getStudy(); } @@ -110,31 +111,20 @@ class JsStudyQueryTask extends JsPatientQueryTask { } async getNextStudyCursor() { - let queryAudit = new AuditManager( - EventType.QUERY, EventOutcomeIndicator.Success, - await this.as.getRemoteAET(), await this.as.getRemoteHostName(), - await this.as.getLocalAET(), await this.as.getLocalHostName() - ); + let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); - let queryAttr = await Attributes.newInstanceAsync(); - await queryAttr.addAll(this.keys); - await queryAttr.addSelected(this.patientAttr, [Tag.PatientID]); - - let queryBuilder = new DimseQueryBuilder(queryAttr, "study"); - let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - queryAudit.onQuery( + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.patientAttr); + let dbQuery = await QueryTaskUtils.getDbQuery(queryAttr, "study"); + queryAuditManager.onQuery( UID.StudyRootQueryRetrieveInformationModelFind, - JSON.stringify(mongoQuery.$match), + JSON.stringify(dbQuery), "UTF-8" ); - let returnKeys = this.getReturnKeys(normalQuery); - - logger.info(`do DIMSE Study query: ${JSON.stringify(mongoQuery.$match)}`); + logger.info(`do DIMSE Study query: ${JSON.stringify(dbQuery)}`); this.studyCursor = await StudyModel.getDimseResultCursor({ - ...mongoQuery.$match - }, returnKeys); + ...dbQuery + }, await QueryTaskUtils.getReturnKeys(queryAttr, "study")); } async auditDicomInstancesAccessed() { diff --git a/dimse/utils.js b/dimse/utils.js index 048c5d95..dbb734aa 100644 --- a/dimse/utils.js +++ b/dimse/utils.js @@ -6,6 +6,10 @@ const { importClass } = require("java-bridge"); const { raccoonConfig } = require("@root/config-class"); const { InstanceLocator } = require("@dcm4che/net/service/InstanceLocator"); const { File } = require("@java-wrapper/java/io/File"); +const { AuditManager } = require("@models/DICOM/audit/auditManager"); +const { EventType } = require("@models/DICOM/audit/eventType"); +const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); +const { Tag } = require("@dcm4che/data/Tag"); /** * * @param {number} tag @@ -95,6 +99,59 @@ async function findOneInstanceFromKeysAttr(keys) { return instance; } +const QUERY_ATTR_SELECTED_TAGS = { + "patient": [Tag.PatientID], + "study": [Tag.PatientID], + "series": [Tag.PatientID, Tag.StudyInstanceUID], + "instance": [Tag.PatientID, Tag.StudyInstanceUID, Tag.SeriesInstanceUID] +}; +class QueryTaskUtils { + /** + * + * @param {import("@dcm4che/net/Association").Association} association + * @returns + */ + static async getAuditManager(association) { + return new AuditManager( + EventType.QUERY, EventOutcomeIndicator.Success, + await association.getRemoteAET(), await association.getRemoteHostName(), + await association.getLocalAET(), await association.getLocalHostName() + ); + } + + static async getQueryAttribute(keys, parentAttr, level = "patient") { + let queryAttr = await Attributes.newInstanceAsync(); + await queryAttr.addAll(keys); + await queryAttr.addSelected(parentAttr, QUERY_ATTR_SELECTED_TAGS[level]); + return queryAttr; + } + + static async getQueryBuilder(queryAttr, level = "patient") { + const { DimseQueryBuilder } = require("./queryBuilder"); + return new DimseQueryBuilder(queryAttr, level); + } + + static async getReturnKeys(queryAttr, level = "patient") { + let queryBuilder = await QueryTaskUtils.getQueryBuilder(queryAttr, level); + let query = await queryBuilder.toNormalQuery(); + let returnKeys = {}; + let queryKeys = Object.keys(query); + for (let i = 0; i < queryKeys.length; i++) { + returnKeys[queryKeys[i].split(".").shift()] = 1; + } + return returnKeys; + } + + static async getDbQuery(queryAttr, level="patient") { + let queryBuilder = await QueryTaskUtils.getQueryBuilder(queryAttr, level); + let normalQuery = await queryBuilder.toNormalQuery(); + let dbQuery = await queryBuilder.getMongoQuery(normalQuery); + + return dbQuery.$match; + } +} + module.exports.intTagToString = intTagToString; module.exports.getInstancesFromKeysAttr = getInstancesFromKeysAttr; -module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr; \ No newline at end of file +module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr; +module.exports.QueryTaskUtils = QueryTaskUtils; \ No newline at end of file From e2df23ef91530067e1e43802c48ea49878336451 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 12 Nov 2023 14:55:19 +0800 Subject: [PATCH 125/365] refactor: extract class for study inject proxies --- dimse/seriesQueryTask.js | 2 +- dimse/studyQueryTask.js | 156 ++++++++++++++++++++++++--------------- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index 6d3b4255..ec0ec19b 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -96,7 +96,7 @@ class JsSeriesQueryTask extends JsStudyQueryTask { await this.seriesQueryTaskInjectMethods.getSeries(); } - while (!this.seriesAttr && await this.studyQueryTaskInjectMethods.findNextStudy()) { + while (!this.seriesAttr && await this.studyQueryTaskInjectProxy.findNextStudy()) { await this.getNextSeriesCursor(); await this.seriesQueryTaskInjectMethods.getSeries(); } diff --git a/dimse/studyQueryTask.js b/dimse/studyQueryTask.js index 60d12afd..cd300a20 100644 --- a/dimse/studyQueryTask.js +++ b/dimse/studyQueryTask.js @@ -4,7 +4,7 @@ const { StudyQueryTask } = require("@chinlinlee/dcm777/net/StudyQueryTask"); const { JsPatientQueryTask } = require("./patientQueryTask"); const { Tag } = require("@dcm4che/data/Tag"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); -const { StudyQueryTaskInjectInterface, createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); +const { createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); const { DimseQueryBuilder } = require("./queryBuilder"); const { StudyModel } = require("@dbModels/study.model"); const { Attributes } = require("@dcm4che/data/Attributes"); @@ -37,82 +37,30 @@ class JsStudyQueryTask extends JsPatientQueryTask { ); await super.get(); - await this.studyQueryTaskInjectMethods.wrappedFindNextStudy(); + await this.studyQueryTaskInjectProxy.wrappedFindNextStudy(); return studyQueryTask; } getQueryTaskInjectProxy() { - /** @type { QueryTaskInjectInterface } */ - this.studyBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.studyAttr); - }, - nextMatch: async () => { - let returnAttr = await Attributes.newInstanceAsync( - await this.patientAttr.size() + await this.studyAttr.size() - ); - await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr]); - await returnAttr.addAll(this.patientAttr); - await returnAttr.addAll(this.studyAttr); - - await this.studyQueryTaskInjectMethods.wrappedFindNextStudy(); - - return returnAttr; - }, - adjust: async (match) => { - return await this.patientAdjust(match); - } - }; - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.studyBasicQueryTaskInjectMethods); + this.queryTaskInjectProxy = new StudyMatchIteratorProxy(this); } - return this.queryTaskInjectProxy; + return this.queryTaskInjectProxy.get(); } getStudyQueryTaskInjectProxy() { - /** @type { StudyQueryTaskInjectInterface } */ - this.studyQueryTaskInjectMethods = { - wrappedFindNextStudy: async () => { - await this.studyQueryTaskInjectMethods.findNextStudy(); - }, - getStudy: async () => { - this.study = await this.studyCursor.next(); - this.auditDicomInstancesAccessed(); - this.studyAttr = this.study ? await this.study.getAttributes() : null; - }, - findNextStudy: async () => { - if (!this.patientAttr) - return false; - - if (!this.studyAttr) { - await this.getNextStudyCursor(); - await this.studyQueryTaskInjectMethods.getStudy(); - } else { - await this.studyQueryTaskInjectMethods.getStudy(); - } - - while (!this.studyAttr && await this.patientQueryTaskProxy.findNextPatient()) { - await this.getNextStudyCursor(); - await this.studyQueryTaskInjectMethods.getStudy(); - } - - return !_.isNull(this.studyAttr); - } - }; - if (!this.studyQueryTaskInjectProxy) { - this.studyQueryTaskInjectProxy = createStudyQueryTaskInjectProxy(this.studyQueryTaskInjectMethods); + this.studyQueryTaskInjectProxy = new StudyQueryTaskInjectProxy(this); } - return this.studyQueryTaskInjectProxy; + return this.studyQueryTaskInjectProxy.get(); } async getNextStudyCursor() { let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); - + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.patientAttr); let dbQuery = await QueryTaskUtils.getDbQuery(queryAttr, "study"); queryAuditManager.onQuery( @@ -136,10 +84,98 @@ class JsStudyQueryTask extends JsPatientQueryTask { await this.as.getRemoteAET(), await this.as.getRemoteHostName(), await this.as.getLocalAET(), await this.as.getLocalHostName() ); - let studyUID = _.get(this.study, "0020000D.Value.0"); auditManager.onDicomInstancesAccessed([studyUID]); } } +class StudyQueryTaskInjectProxy { + constructor(studyQueryTask) { + /** @type { JsStudyQueryTask } */ + this.studyQueryTask = studyQueryTask; + } + + get() { + return createStudyQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + wrappedFindNextStudy: this.wrappedFindNextStudy.bind(this), + getStudy: this.getStudy.bind(this), + findNextStudy: this.findNextStudy.bind(this) + }; + } + + async wrappedFindNextStudy() { + await this.findNextStudy(); + } + + async getStudy() { + this.studyQueryTask.study = await this.studyQueryTask.studyCursor.next(); + this.studyQueryTask.auditDicomInstancesAccessed(); + this.studyQueryTask.studyAttr = this.studyQueryTask.study ? await this.studyQueryTask.study.getAttributes() : null; + } + + async findNextStudy() { + if (!this.studyQueryTask.patientAttr) + return false; + + if (!this.studyQueryTask.studyAttr) { + await this.studyQueryTask.getNextStudyCursor(); + await this.getStudy(); + } else { + await this.getStudy(); + } + + while (!this.studyQueryTask.studyAttr && await this.studyQueryTask.patientQueryTaskProxy.findNextPatient()) { + await this.studyQueryTask.getNextStudyCursor(); + await this.getStudy(); + } + + return !_.isNull(this.studyQueryTask.studyAttr); + } +} + +class StudyMatchIteratorProxy { + constructor(studyQueryTask) { + /** @type { JsStudyQueryTask } */ + this.studyQueryTask = studyQueryTask; + } + + get() { + return createQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + hasMoreMatches: () => { + return !_.isNull(this.studyQueryTask.studyAttr); + }, + nextMatch: async () => { + let returnAttr = await Attributes.newInstanceAsync( + await this.studyQueryTask.patientAttr.size() + await this.studyQueryTask.studyAttr.size() + ); + await Attributes.unifyCharacterSets([ + this.studyQueryTask.patientAttr, + this.studyQueryTask.studyAttr + ]); + await returnAttr.addAll(this.studyQueryTask.patientAttr); + await returnAttr.addAll(this.studyQueryTask.studyAttr); + + await this.studyQueryTask.studyQueryTaskInjectProxy.wrappedFindNextStudy(); + + return returnAttr; + }, + adjust: async (match) => { + return this.studyQueryTask.patientAdjust(match); + } + }; + } +} + module.exports.JsStudyQueryTask = JsStudyQueryTask; \ No newline at end of file From 4c870bfde605ff86684570c8ffb36534846feac4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 12 Nov 2023 15:34:43 +0800 Subject: [PATCH 126/365] refactor: extract class for series inject proxies --- dimse/instanceQueryTask.js | 2 +- dimse/seriesQueryTask.js | 157 +++++++++++++++++++++++-------------- 2 files changed, 99 insertions(+), 60 deletions(-) diff --git a/dimse/instanceQueryTask.js b/dimse/instanceQueryTask.js index 04647608..d61f54ce 100644 --- a/dimse/instanceQueryTask.js +++ b/dimse/instanceQueryTask.js @@ -98,7 +98,7 @@ class JsInstanceQueryTask extends JsSeriesQueryTask { await this.instanceQueryTaskInjectMethods.getInstance(); } - while (!this.instanceAttr && await this.seriesQueryTaskInjectMethods.findNextSeries()) { + while (!this.instanceAttr && await this.seriesQueryTaskInjectProxy.findNextSeries()) { await this.getNextInstanceCursor(); await this.instanceQueryTaskInjectMethods.getInstance(); } diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index ec0ec19b..d963a1db 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -38,78 +38,25 @@ class JsSeriesQueryTask extends JsStudyQueryTask { ); await super.get(); - await this.seriesQueryTaskInjectMethods.wrappedFindNextSeries(); + await this.seriesQueryTaskInjectProxy.wrappedFindNextSeries(); return seriesQueryTask; } getQueryTaskInjectProxy() { - /** @type { QueryTaskInjectInterface } */ - this.seriesBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.seriesAttr); - }, - nextMatch: async () => { - let returnAttr = await Attributes.newInstanceAsync( - await this.patientAttr.size() + await this.studyAttr.size() + await this.seriesAttr.size() - ); - await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr, this.seriesAttr]); - await returnAttr.addAll(this.patientAttr); - await returnAttr.addAll(this.studyAttr, true); - await returnAttr.addAll(this.seriesAttr, true); - - await this.seriesQueryTaskInjectMethods.wrappedFindNextSeries(); - - return returnAttr; - }, - adjust: async (match) => { - return await this.patientAdjust(match); - } - }; - - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.seriesBasicQueryTaskInjectMethods); + if (!this.matchIteratorProxy) { + this.matchIteratorProxy = createQueryTaskInjectProxy(this.seriesBasicQueryTaskInjectMethods); } - return this.queryTaskInjectProxy; + return this.matchIteratorProxy; } getSeriesQueryTaskInjectProxy() { - /** @type {import("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTaskInject").SeriesQueryTaskInjectInterface} */ - this.seriesQueryTaskInjectMethods = { - wrappedFindNextSeries: async () => { - await this.seriesQueryTaskInjectMethods.findNextSeries(); - }, - getSeries: async () => { - this.series = await this.seriesCursor.next(); - if (this.series) this.auditDicomInstancesAccessed(); - this.seriesAttr = this.series ? await this.series.getAttributes() : null; - }, - findNextSeries: async () => { - if (!this.studyAttr) - return false; - - if (!this.seriesAttr) { - await this.getNextSeriesCursor(); - await this.seriesQueryTaskInjectMethods.getSeries(); - } else { - await this.seriesQueryTaskInjectMethods.getSeries(); - } - - while (!this.seriesAttr && await this.studyQueryTaskInjectProxy.findNextStudy()) { - await this.getNextSeriesCursor(); - await this.seriesQueryTaskInjectMethods.getSeries(); - } - - return !_.isNull(this.seriesAttr); - } - }; - if (!this.seriesQueryTaskInjectProxy) { - this.seriesQueryTaskInjectProxy = createSeriesQueryTaskInjectProxy(this.seriesQueryTaskInjectMethods); + this.seriesQueryTaskInjectProxy = new SeriesQueryTaskInjectProxy(this); } - return this.seriesQueryTaskInjectProxy; + return this.seriesQueryTaskInjectProxy.get(); } async getNextSeriesCursor() { @@ -129,5 +76,97 @@ class JsSeriesQueryTask extends JsStudyQueryTask { }, await QueryTaskUtils.getReturnKeys(queryAttr, "series")); } } +class SeriesQueryTaskInjectProxy { + constructor(seriesQueryTask) { + /** @type { JsSeriesQueryTask } */ + this.seriesQueryTask = seriesQueryTask; + } + + get() { + return new createSeriesQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + wrappedFindNextSeries: this.wrappedFindNextSeries.bind(this), + getSeries: this.getSeries.bind(this), + findNextSeries: this.findNextSeries.bind(this) + }; + } + + async wrappedFindNextSeries() { + await this.findNextSeries(); + } + + async getSeries() { + this.seriesQueryTask.series = await this.seriesQueryTask.seriesCursor.next(); + if (this.seriesQueryTask.series) this.seriesQueryTask.auditDicomInstancesAccessed(); + this.seriesQueryTask.seriesAttr = this.seriesQueryTask.series ? await this.seriesQueryTask.series.getAttributes() : null; + } + + async findNextSeries() { + if (!this.seriesQueryTask.studyAttr) + return false; + + if (!this.seriesQueryTask.seriesAttr) { + await this.seriesQueryTask.getNextSeriesCursor(); + await this.getSeries(); + } else { + await this.getSeries(); + } + + while (!this.seriesQueryTask.seriesAttr && await this.seriesQueryTask.studyQueryTaskInjectProxy.findNextStudy()) { + await this.seriesQueryTask.getNextSeriesCursor(); + await this.getSeries(); + } + + return !_.isNull(this.seriesQueryTask.seriesAttr); + } +} + +class SeriesMatchIteratorProxy { + constructor(seriesQueryTask) { + /** @type {JsSeriesQueryTask} */ + this.seriesQueryTask = seriesQueryTask; + } + + get() { + return createQueryTaskInjectProxy(this.getProxyMethods()); + } + + getProxyMethods() { + return { + hasMoreMatches: () => { + return !_.isNull(this.seriesQueryTask.seriesAttr); + }, + nextMatch: async () => { + let returnAttr = await Attributes.newInstanceAsync( + await this.seriesQueryTask.patientAttr.size() + + await this.seriesQueryTask.studyAttr.size() + + await this.seriesQueryTask.seriesAttr.size() + ); + + await Attributes.unifyCharacterSets([ + this.seriesQueryTask.patientAttr, + this.seriesQueryTask.studyAttr, + this.seriesQueryTask.seriesAttr + ]); + + await returnAttr.addAll(this.seriesQueryTask.patientAttr); + await returnAttr.addAll(this.seriesQueryTask.studyAttr, true); + await returnAttr.addAll(this.seriesQueryTask.seriesAttr, true); + + await this.seriesQueryTask.seriesQueryTaskProxy.wrappedFindNextSeries(); + + return returnAttr; + }, + adjust: async (match) => { + return this.patientAdjust(match); + } + }; + } +} module.exports.JsSeriesQueryTask = JsSeriesQueryTask; \ No newline at end of file From a6129c238207118bbdd5fd41af1e8a0663113d55 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 12 Nov 2023 16:08:18 +0800 Subject: [PATCH 127/365] chore: jscpd ignore require libraries code --- .jscpd.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.jscpd.json b/.jscpd.json index da1246e7..366bffd8 100644 --- a/.jscpd.json +++ b/.jscpd.json @@ -11,6 +11,9 @@ "docs/**", "test/**" ], + "ignorePattern": [ + "const .* require.*" + ], "absolute": true, "gitignore": true } \ No newline at end of file From d85729a9d29c9cb568b469da16b306c9ab086754 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 12 Nov 2023 16:34:43 +0800 Subject: [PATCH 128/365] refactor: duplicate code for `getInstancesFromKeysAttr` and `findOneInstanceFromKeysAttr` --- dimse/utils.js | 45 +++++++++++++++------------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/dimse/utils.js b/dimse/utils.js index dbb734aa..61cf2874 100644 --- a/dimse/utils.js +++ b/dimse/utils.js @@ -18,29 +18,26 @@ function intTagToString(tag) { return tag.toString(16).padStart(8, "0").toUpperCase(); } +const INSTANCE_RETURN_KEYS = { + "instancePath": 1, + "00020010": 1, + "00080016": 1, + "00080018": 1, + "0020000D": 1, + "0020000E": 1 +}; + /** * * @param {Attributes} keys * @returns */ async function getInstancesFromKeysAttr(keys) { - const { DimseQueryBuilder } = require("./queryBuilder"); - let queryBuilder = new DimseQueryBuilder(keys, "instance"); - let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - - let returnKeys = { - "instancePath": 1, - "00020010": 1, - "00080016": 1, - "00080018": 1, - "0020000D": 1, - "0020000E": 1 - }; + let dbQuery = await QueryTaskUtils.getDbQuery(keys, "instance"); let instances = await mongoose.model("dicom").find({ - ...mongoQuery.$match - }, returnKeys).setOptions({ + ...dbQuery + }, INSTANCE_RETURN_KEYS).setOptions({ strictQuery: false }).exec(); const JArrayList = await importClass("java.util.ArrayList"); @@ -76,23 +73,11 @@ async function getInstancesFromKeysAttr(keys) { * @returns */ async function findOneInstanceFromKeysAttr(keys) { - const { DimseQueryBuilder } = require("./queryBuilder"); - let queryBuilder = new DimseQueryBuilder(keys, "instance"); - let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - - let returnKeys = { - "instancePath": 1, - "00020010": 1, - "00080016": 1, - "00080018": 1, - "0020000D": 1, - "0020000E": 1 - }; + let dbQuery = await QueryTaskUtils.getDbQuery(keys, "instance"); let instance = await mongoose.model("dicom").findOne({ - ...mongoQuery.$match - }, returnKeys).setOptions({ + ...dbQuery + }, INSTANCE_RETURN_KEYS).setOptions({ strictQuery: false }).exec(); From 3bbcfec162796f43c5136d8eae9266b8cf2db2d1 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 17 Nov 2023 20:47:56 +0800 Subject: [PATCH 129/365] fix: missing use series match iterator proxy --- dimse/seriesQueryTask.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index d963a1db..66b55aca 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -45,10 +45,10 @@ class JsSeriesQueryTask extends JsStudyQueryTask { getQueryTaskInjectProxy() { if (!this.matchIteratorProxy) { - this.matchIteratorProxy = createQueryTaskInjectProxy(this.seriesBasicQueryTaskInjectMethods); + this.matchIteratorProxy = new SeriesMatchIteratorProxy(this); } - return this.matchIteratorProxy; + return this.matchIteratorProxy.get(); } getSeriesQueryTaskInjectProxy() { From 320896f53b733f91e8837d9ff3e4cae52ea59d1b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 17 Nov 2023 22:11:32 +0800 Subject: [PATCH 130/365] fix: series match iterator proxy is not working # Problems - line 163: Incorrect property - line 166: missing seriesQueryTask --- dimse/seriesQueryTask.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index 66b55aca..47deb050 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -133,7 +133,9 @@ class SeriesMatchIteratorProxy { } get() { - return createQueryTaskInjectProxy(this.getProxyMethods()); + return createQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); } getProxyMethods() { @@ -158,12 +160,12 @@ class SeriesMatchIteratorProxy { await returnAttr.addAll(this.seriesQueryTask.studyAttr, true); await returnAttr.addAll(this.seriesQueryTask.seriesAttr, true); - await this.seriesQueryTask.seriesQueryTaskProxy.wrappedFindNextSeries(); + await this.seriesQueryTask.seriesQueryTaskInjectProxy.wrappedFindNextSeries(); return returnAttr; }, adjust: async (match) => { - return this.patientAdjust(match); + return this.seriesQueryTask.patientAdjust(match); } }; } From ffb1e5d9f4ad239cdcf50e678b438cf617d30667 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 17 Nov 2023 22:15:39 +0800 Subject: [PATCH 131/365] refactor(dimse): instance query task match iterator proxy and instance query task proxy This commit optimizes the instance query task by refactoring the code and improving its performance. It introduces two new classes, InstanceQueryTaskInjectProxy and InstanceMatchIteratorProxy, that help with injecting methods and handling matches. These classes improve the efficiency and maintainability of the code by encapsulating related functionalities. The code changes include: - Refactoring the `getQueryTaskInjectProxy` method to use the new InstanceMatchIteratorProxy class. - Refactoring the `getInstanceQueryTaskInjectProxy` method to use the new InstanceQueryTaskInjectProxy class. - Implementing the InstanceMatchIteratorProxy class to handle methods related to matching instances. - Implementing the InstanceQueryTaskInjectProxy class to handle methods related to querying instances. --- dimse/instanceQueryTask.js | 166 +++++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 61 deletions(-) diff --git a/dimse/instanceQueryTask.js b/dimse/instanceQueryTask.js index d61f54ce..393147de 100644 --- a/dimse/instanceQueryTask.js +++ b/dimse/instanceQueryTask.js @@ -40,83 +40,29 @@ class JsInstanceQueryTask extends JsSeriesQueryTask { ); await super.get(); - await this.instanceQueryTaskInjectMethods.wrappedFindNextInstance(); + await this.instanceQueryTaskInjectProxy.getProxyMethods().wrappedFindNextInstance(); return instanceQueryTask; } getQueryTaskInjectProxy() { - this.instanceBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.instanceAttr); - }, - nextMatch: async () => { - let returnAttr = await Attributes.newInstanceAsync( - await this.patientAttr.size() + await this.studyAttr.size() + await this.seriesAttr.size() + await this.instanceAttr.size() - ); - await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr, this.seriesAttr, this.instanceAttr]); - await returnAttr.addAll(this.patientAttr); - await returnAttr.addAll(this.studyAttr, true); - await returnAttr.addAll(this.seriesAttr, true); - await returnAttr.addAll(this.instanceAttr, true); - - await this.instanceQueryTaskInjectMethods.wrappedFindNextInstance(); - - return returnAttr; - }, - adjust: async (match) => { - return await this.patientAdjust(match); - } - }; - - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.instanceBasicQueryTaskInjectMethods); + if (!this.matchIteratorProxy) { + this.matchIteratorProxy = new InstanceMatchIteratorProxy(this); } - - return this.queryTaskInjectProxy; + return this.matchIteratorProxy.get(); } getInstanceQueryTaskInjectProxy() { - /** @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTaskInject").InstanceQueryTaskInjectInterface } */ - this.instanceQueryTaskInjectMethods = { - wrappedFindNextInstance: async () => { - await this.instanceQueryTaskInjectMethods.findNextInstance(); - }, - getInstance: async () => { - this.instance = await this.instanceCursor.next(); - if (this.instance) this.auditDicomInstancesAccessed(); - this.instanceAttr = this.instance ? await this.instance.getAttributes() : null; - }, - findNextInstance: async () => { - if (!this.seriesAttr) - return false; - - if (!this.instanceAttr) { - await this.getNextInstanceCursor(); - await this.instanceQueryTaskInjectMethods.getInstance(); - } else { - await this.instanceQueryTaskInjectMethods.getInstance(); - } - - while (!this.instanceAttr && await this.seriesQueryTaskInjectProxy.findNextSeries()) { - await this.getNextInstanceCursor(); - await this.instanceQueryTaskInjectMethods.getInstance(); - } - - return _.isNull(this.instanceAttr); - } - }; - if (!this.instanceQueryTaskInjectProxy) { - this.instanceQueryTaskInjectProxy = createInstanceQueryTaskInjectProxy(this.instanceQueryTaskInjectMethods); + this.instanceQueryTaskInjectProxy = new InstanceQueryTaskInjectProxy(this); } - return this.instanceQueryTaskInjectProxy; + return this.instanceQueryTaskInjectProxy.get(); } async getNextInstanceCursor() { let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); - + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.seriesAttr); let dbQuery = await QueryTaskUtils.getDbQuery(queryAttr, "instance"); queryAuditManager.onQuery( @@ -133,4 +79,102 @@ class JsInstanceQueryTask extends JsSeriesQueryTask { } +class InstanceQueryTaskInjectProxy { + constructor(instanceQueryTask) { + /** @type { JsInstanceQueryTask } */ + this.instanceQueryTask = instanceQueryTask; + } + + get() { + return new createInstanceQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + wrappedFindNextInstance: this.wrappedFindNextInstance.bind(this), + getInstance: this.getInstance.bind(this), + findNextInstance: this.findNextInstance.bind(this) + }; + } + + async wrappedFindNextInstance() { + await this.findNextInstance(); + } + + async findNextInstance() { + if (!this.instanceQueryTask.seriesAttr) + return false; + + if (!this.instanceQueryTask.instanceAttr) { + await this.instanceQueryTask.getNextInstanceCursor(); + await this.getInstance(); + } else { + await this.getInstance(); + } + + while (!this.instanceQueryTask.instanceAttr && await this.instanceQueryTask.seriesQueryTaskInjectProxy.findNextSeries()) { + await this.getNextInstanceCursor(); + await this.getInstance(); + } + + return !_.isNull(this.instanceQueryTask.instanceAttr); + } + + async getInstance() { + this.instanceQueryTask.instance = await this.instanceQueryTask.instanceCursor.next(); + if (this.instanceQueryTask.instance) this.instanceQueryTask.auditDicomInstancesAccessed(); + this.instanceQueryTask.instanceAttr = this.instanceQueryTask.instance ? await this.instanceQueryTask.instance.getAttributes() : null; + } + +} + +class InstanceMatchIteratorProxy { + constructor(instanceQueryTask) { + /** @type {JsInstanceQueryTask} */ + this.instanceQueryTask = instanceQueryTask; + } + + get() { + return createQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + hasMoreMatches: async () => { + return !_.isNull(this.instanceQueryTask.instanceAttr); + }, + nextMatch: async () => { + let returnAttr = await Attributes.newInstanceAsync( + await this.instanceQueryTask.patientAttr.size() + + await this.instanceQueryTask.studyAttr.size() + + await this.instanceQueryTask.seriesAttr.size() + + await this.instanceQueryTask.instanceAttr.size() + ); + + await Attributes.unifyCharacterSets([ + this.instanceQueryTask.patientAttr, + this.instanceQueryTask.studyAttr, + this.instanceQueryTask.seriesAttr, + this.instanceQueryTask.instanceAttr + ]); + await returnAttr.addAll(this.instanceQueryTask.patientAttr); + await returnAttr.addAll(this.instanceQueryTask.studyAttr, true); + await returnAttr.addAll(this.instanceQueryTask.seriesAttr, true); + await returnAttr.addAll(this.instanceQueryTask.instanceAttr, true); + + await this.instanceQueryTask.instanceQueryTaskInjectProxy.wrappedFindNextInstance(); + + return returnAttr; + }, + adjust: async (match) => { + return await this.instanceQueryTask.patientAdjust(match); + } + }; + } +} + module.exports.JsInstanceQueryTask = JsInstanceQueryTask; \ No newline at end of file From 6bedb35b734438d00da07d16fabf56600d82a552 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 17 Nov 2023 22:30:49 +0800 Subject: [PATCH 132/365] refactor: use `QueryTaskUtils` in PatientQueryTask --- dimse/patientQueryTask.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/dimse/patientQueryTask.js b/dimse/patientQueryTask.js index e6cb70ce..c5c8882c 100644 --- a/dimse/patientQueryTask.js +++ b/dimse/patientQueryTask.js @@ -14,6 +14,7 @@ const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { UID } = require("@dcm4che/data/UID"); +const { QueryTaskUtils } = require("./utils"); class JsPatientQueryTask { @@ -114,26 +115,19 @@ class JsPatientQueryTask { } async initCursor() { - let queryAudit = new AuditManager( - EventType.QUERY, - EventOutcomeIndicator.Success, - await this.as.getRemoteAET(), await this.as.getRemoteHostName(), - await this.as.getLocalAET(), await this.as.getLocalHostName() - ); - let queryBuilder = new DimseQueryBuilder(this.keys, "patient"); - let normalQuery = await queryBuilder.toNormalQuery(); - let mongoQuery = await queryBuilder.getMongoQuery(normalQuery); - queryAudit.onQuery( + let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); + let dbQuery = await QueryTaskUtils.getDbQuery(this.keys, "patient"); + queryAuditManager.onQuery( UID.PatientRootQueryRetrieveInformationModelFind, - JSON.stringify(mongoQuery.$match), + JSON.stringify(dbQuery), "UTF-8" ); - let returnKeys = this.getReturnKeys(normalQuery); + let returnKeys = await QueryTaskUtils.getReturnKeys(this.keys, "patient"); - logger.info(`do DIMSE Patient query: ${JSON.stringify(mongoQuery.$match)}`); + logger.info(`do DIMSE Patient query: ${JSON.stringify(dbQuery)}`); this.cursor = await PatientModel.getDimseResultCursor({ - ...mongoQuery.$match + ...dbQuery }, returnKeys); } } From c4e766c4c924f24a69fc875247dc38688e63db39 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 17 Nov 2023 22:42:03 +0800 Subject: [PATCH 133/365] refactor: rename `getMongoQuery` to build in queryBuilder --- dimse/queryBuilder.js | 2 +- dimse/utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dimse/queryBuilder.js b/dimse/queryBuilder.js index 4aeaa73f..b49c84ea 100644 --- a/dimse/queryBuilder.js +++ b/dimse/queryBuilder.js @@ -39,7 +39,7 @@ class DimseQueryBuilder { return clonedQuery; } - async getMongoQuery(query) { + async build(query) { return await convertRequestQueryToMongoQuery( this.cleanEmptyQuery(query) ); diff --git a/dimse/utils.js b/dimse/utils.js index 61cf2874..33e2c2ec 100644 --- a/dimse/utils.js +++ b/dimse/utils.js @@ -130,7 +130,7 @@ class QueryTaskUtils { static async getDbQuery(queryAttr, level="patient") { let queryBuilder = await QueryTaskUtils.getQueryBuilder(queryAttr, level); let normalQuery = await queryBuilder.toNormalQuery(); - let dbQuery = await queryBuilder.getMongoQuery(normalQuery); + let dbQuery = await queryBuilder.build(normalQuery); return dbQuery.$match; } From 8fda28038508ef98e8e9a0afec297dc2b73a5336 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 17 Nov 2023 23:00:53 +0800 Subject: [PATCH 134/365] feat: add `getValue` and `getValues` in base dicom json --- models/DICOM/dicom-json-model.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index a0ee5908..9359fd3f 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -60,10 +60,14 @@ class BaseDicomJson { return _.get(this.dicomJson, tag, undefined); } - getValue(tag) { + getValues(tag) { return _.get(this.dicomJson, `${tag}.Value`, undefined); } + getValue(tag) { + return _.get(this.dicomJson, `${tag}.Value.0`, undefined); + } + getSequenceItem(tag) { return JSONPath({ path: `$..${tag}`, From 8b4cf9ffcc91b34c2237d251f029fef96eef238b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 17 Nov 2023 23:01:08 +0800 Subject: [PATCH 135/365] refactor: remove unused fn --- dimse/patientQueryTask.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dimse/patientQueryTask.js b/dimse/patientQueryTask.js index c5c8882c..b72705f6 100644 --- a/dimse/patientQueryTask.js +++ b/dimse/patientQueryTask.js @@ -105,15 +105,6 @@ class JsPatientQueryTask { return basicAd; } - getReturnKeys(query) { - let returnKeys = {}; - let queryKeys = Object.keys(query); - for (let i = 0; i < queryKeys.length; i++) { - returnKeys[queryKeys[i].split(".").shift()] = 1; - } - return returnKeys; - } - async initCursor() { let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); let dbQuery = await QueryTaskUtils.getDbQuery(this.keys, "patient"); From e1a8eda724857aadcdd5eac206cf7466a4dc2d30 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 11:21:42 +0800 Subject: [PATCH 136/365] refactor: sync `RetrieveTaskImpl` require location --- dimse-sql/c-get.js | 2 +- dimse-sql/c-move.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dimse-sql/c-get.js b/dimse-sql/c-get.js index 625c3ac3..032489ff 100644 --- a/dimse-sql/c-get.js +++ b/dimse-sql/c-get.js @@ -3,7 +3,7 @@ const { createCGetSCPInjectProxy } = require("@java-wrapper/org/github/chinlinle const { default: SimpleCGetSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCGetSCP"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); const { getInstancesFromKeysAttr } = require("./utils"); -const { default: RetrieveTaskImpl } = require("@dcm4che/tool/dcmqrscp/RetrieveTaskImpl"); +const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); const { Dimse } = require("@dcm4che/net/Dimse"); const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); const { default: QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); diff --git a/dimse-sql/c-move.js b/dimse-sql/c-move.js index c9983402..ae3aba21 100644 --- a/dimse-sql/c-move.js +++ b/dimse-sql/c-move.js @@ -13,7 +13,7 @@ const { UID } = require("@dcm4che/data/UID"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); const { default: AAssociateRQ } = require("@dcm4che/net/pdu/AAssociateRQ"); const { default: Connection } = require("@dcm4che/net/Connection"); -const { default: RetrieveTaskImpl } = require("@dcm4che/tool/dcmqrscp/RetrieveTaskImpl"); +const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); const { Dimse } = require("@dcm4che/net/Dimse"); const { getInstancesFromKeysAttr } = require("./utils"); From 34b84a0aead12776b955b77f30ea14cafb10c1e9 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 11:23:56 +0800 Subject: [PATCH 137/365] refactor: update module alias to sql version --- jsconfig.json | 4 ++-- models/sql/initializer.js | 1 + package.json | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 models/sql/initializer.js diff --git a/jsconfig.json b/jsconfig.json index 1151bfbb..b186d335 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -8,8 +8,8 @@ "@error/*" : ["./error/*"], "@root/*": ["./*"], "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], - "@dbModels/*": ["./models/mongodb/models/*"], - "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"] + "@dbModels/*": ["./models/sql/models/*"], + "@dicom-json-model": ["./models/sql/dicom-json-model.js"] } }, "exclude": [ diff --git a/models/sql/initializer.js b/models/sql/initializer.js new file mode 100644 index 00000000..ccb86508 --- /dev/null +++ b/models/sql/initializer.js @@ -0,0 +1 @@ +require("./init").then(()=> console.log("Sequelize initialized")); \ No newline at end of file diff --git a/package.json b/package.json index a5a3dd26..2de72f28 100644 --- a/package.json +++ b/package.json @@ -61,9 +61,9 @@ "@error": "./error", "@root": "./", "@chinlinlee": "./models/DICOM/dcm4che/wrapper/org/github/chinlinlee", - "@dbModels": "./models/mongodb/models", - "@dbInitializer": "./models/mongodb/index.js", - "@dicom-json-model": "./models/DICOM/dicom-json-model.js" + "@dbModels": "./models/sql/models", + "@dbInitializer": "./models/sql/initializer.js", + "@dicom-json-model": "./models/sql/dicom-json-model.js" }, "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", From 16aab064c851ac8d3f3bd4d6f75f8d1b837cf964 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 11:24:14 +0800 Subject: [PATCH 138/365] chore: remove not implement code in sql --- .../dicom-web/controller/WADO-RS/deletion/service/delete.js | 4 ---- routes.js | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js index a0973165..15476362 100644 --- a/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -1,7 +1,3 @@ -const _ = require("lodash"); -const dicomSeriesModel = require("../../../../../../models/mongodb/models/dicomSeries"); -const dicomModel = require("../../../../../../models/mongodb/models/dicom"); -const fsP = require("fs/promises"); const { NotFoundInstanceError } = require("../../../../../../error/dicom-instance"); const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); diff --git a/routes.js b/routes.js index c8e9a37e..83ec2586 100644 --- a/routes.js +++ b/routes.js @@ -27,7 +27,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api-sql/dicom-web/delete.route")); - app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); + // app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); app.use("/wado", require("./api-sql/WADO-URI")); }; From af7e9d61b1a9bee1de3b3d6f47487f02dbf08ea0 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 11:26:00 +0800 Subject: [PATCH 139/365] refactor: sync thumbnail server `getUidsString` refactor --- .../dicom-web/controller/WADO-RS/service/thumbnail.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 3505388e..07c10cd3 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -8,6 +8,7 @@ const { InstanceThumbnailFactory } = require("@root/api/dicom-web/controller/WADO-RS/service/thumbnail.service"); const { InstanceModel } = require("@models/sql/models/instance.model"); +const { getUidsString } = require("@root/api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class SqlThumbnailService extends ThumbnailService { /** @@ -62,7 +63,7 @@ class SqlThumbnailService extends ThumbnailService { this.response.writeHead(404, { "Content-Type": "application/dicom+json" }); - let notFoundMessage = errorResponse.getNotFoundErrorMessage(`Not Found, ${this.thumbnailFactory.getUidsString()}`); + let notFoundMessage = errorResponse.getNotFoundErrorMessage(`Not Found, ${getUidsString(this.thumbnailFactory.uids)}`); let notFoundMessageStr = JSON.stringify(notFoundMessage); From f5f1e42d7dac88ded97dc00b099e02f7f2581934 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 15:36:35 +0800 Subject: [PATCH 140/365] fix(sql): missing ApiLogger in WadoUriService This commit updates the constructor of the WadoUriService class in the retrieveInstance.js file. It now includes an instance of the ApiLogger class as a parameter. Similarly, the constructor of the SqlWadoUriService class in the WADO-URI.service.js file is also updated to include the ApiLogger parameter. This change ensures that the logger is properly passed to the service classes. --- api-sql/WADO-URI/controller/retrieveInstance.js | 2 +- api-sql/WADO-URI/service/WADO-URI.service.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api-sql/WADO-URI/controller/retrieveInstance.js b/api-sql/WADO-URI/controller/retrieveInstance.js index 71c2550d..5224425d 100644 --- a/api-sql/WADO-URI/controller/retrieveInstance.js +++ b/api-sql/WADO-URI/controller/retrieveInstance.js @@ -5,8 +5,8 @@ const { ApiLogger } = require("@root/utils/logs/api-logger"); class RetrieveSingleInstanceController extends Controller { constructor(req, res) { super(req, res); - this.service = new WadoUriService(req, res); this.logger = new ApiLogger(this.request, "WADO-URI"); + this.service = new WadoUriService(req, res, this.logger); } async mainProcess() { diff --git a/api-sql/WADO-URI/service/WADO-URI.service.js b/api-sql/WADO-URI/service/WADO-URI.service.js index 4e4f617f..681070aa 100644 --- a/api-sql/WADO-URI/service/WADO-URI.service.js +++ b/api-sql/WADO-URI/service/WADO-URI.service.js @@ -8,6 +8,7 @@ const Magick = require("@models/magick"); const { NotFoundInstanceError, InvalidFrameNumberError, InstanceGoneError } = require("@error/dicom-instance"); const { WadoUriService } = require("@root/api/WADO-URI/service/WADO-URI.service"); const { InstanceModel } = require("@models/sql/models/instance.model"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); class SqlWadoUriService extends WadoUriService{ /** @@ -15,8 +16,9 @@ class SqlWadoUriService extends WadoUriService{ * @param {import("http").IncomingMessage} req * @param {import("http").ServerResponse} res */ - constructor(req, res) { + constructor(req, res, apiLogger) { super(req, res); + this.apiLogger = apiLogger; } async getDicomInstancePathObj() { From ab45bf88eb1b481f5e9ef030307b836092c621c9 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 15:37:39 +0800 Subject: [PATCH 141/365] fix(sql): missing retrieve audit injection in c-get.js and c-move.js - Add retrieval audit service and audit injection proxy classes to c-get.js and c-move.js. - Implement the getAuditInject method in both classes to create and return an instance of the audit injection proxy. --- dimse-sql/c-get.js | 27 +++++++++++++++++++++++++++ dimse-sql/c-move.js | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/dimse-sql/c-get.js b/dimse-sql/c-get.js index 032489ff..239d28e2 100644 --- a/dimse-sql/c-get.js +++ b/dimse-sql/c-get.js @@ -7,6 +7,8 @@ const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskIm const { Dimse } = require("@dcm4che/net/Dimse"); const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); const { default: QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); +const { DimseRetrieveAuditService } = require("@root/dimse/service/retrieveAudit.service"); +const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); class JsCGetScp { constructor() { } @@ -99,6 +101,7 @@ class JsCGetScp { instances, as, withoutBulkData, + await this.getAuditInject(as), 0 ); await retrieveTask.setSendPendingRSP(false); @@ -109,6 +112,30 @@ class JsCGetScp { return cGetScpInjectProxyMethods; }; + + getAuditInject(association) { + let dimseRetrieveAuditService = new DimseRetrieveAuditService( + association, + null, + null + ); + + return createRetrieveAuditInjectProxy( + { + onBeginTransferringDICOMInstances: async (studyUIDs) => { + dimseRetrieveAuditService.studyUID = studyUIDs[0]; + await dimseRetrieveAuditService.onBeginRetrieve(); + }, + onDicomInstancesTransferred: async (studyUIDs) => { + dimseRetrieveAuditService.studyUID = studyUIDs[0]; + await dimseRetrieveAuditService.completedRetrieve(); + }, + setEventResult: (eventResult) => { + dimseRetrieveAuditService.eventResult = eventResult; + } + } + ); + } } module.exports.JsCGetScp = JsCGetScp; \ No newline at end of file diff --git a/dimse-sql/c-move.js b/dimse-sql/c-move.js index ae3aba21..f3f93a59 100644 --- a/dimse-sql/c-move.js +++ b/dimse-sql/c-move.js @@ -16,6 +16,8 @@ const { default: Connection } = require("@dcm4che/net/Connection"); const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); const { Dimse } = require("@dcm4che/net/Dimse"); const { getInstancesFromKeysAttr } = require("./utils"); +const { DimseRetrieveAuditService } = require("@root/dimse/service/retrieveAudit.service"); +const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); class JsCMoveScp { constructor(dcmQrScp) { @@ -84,6 +86,8 @@ class JsCMoveScp { let aAssociateRq = await this.makeAAssociateRQ_(await as.getLocalAET(), moveDest, instances); let storeAssociation = await this.openStoreAssociation_(as, remote, aAssociateRq); + + let auditInject = await this.getAuditInject(as); let retrieveTask = await RetrieveTaskImpl.newInstanceAsync( Dimse.C_MOVE_RQ, as, @@ -92,7 +96,8 @@ class JsCMoveScp { instances, storeAssociation, false, - 0 + 0, + auditInject ); await retrieveTask.setSendPendingRSPInterval(0); return retrieveTask; @@ -106,6 +111,37 @@ class JsCMoveScp { return cMoveScpInjectProxyMethods; } + getAuditInject(association) { + let dimseRetrieveAuditService = new DimseRetrieveAuditService( + association, + null, + null + ); + if (this.auditInject) + return this.auditInject; + + this.auditInject = createRetrieveAuditInjectProxy( + { + onBeginTransferringDICOMInstances: async (studyUID) => { + dimseRetrieveAuditService.studyUID = studyUID; + await dimseRetrieveAuditService.onBeginRetrieve(); + }, + onDicomInstancesTransferred: async (studyUID) => { + dimseRetrieveAuditService.studyUID = studyUID; + await dimseRetrieveAuditService.completedRetrieve(); + }, + setEventResult: (eventResult) => { + dimseRetrieveAuditService.eventResult = eventResult; + } + }, + { + keepAsDaemon: true + } + ); + + return this.auditInject; + } + /** * @private * @param {string} callingAet From 2aed4920e383fe40ceb5576ec4f5cb0b308dfcc3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 15:40:47 +0800 Subject: [PATCH 142/365] fix(sql): can not store `DT` for 0008002A field # Problems - `YYYYMMDDhhmmss` can not directly parsing to date of sequelize # Solution - use moment parse to iso string --- models/sql/po/instance.po.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index cd9d1dba..3e899e53 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -184,7 +184,7 @@ class InstancePersistentObject { x00080016: this.x00080016, x00080022: this.x00080022 ? this.x00080022 : undefined, x00080023: this.x00080023, - x0008002A: this.x0008002A ? this.x0008002A : undefined, + x0008002A: this.x0008002A ? moment(this.x0008002A, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString(): undefined, x00080033: this.x00080033 ? Number(this.x00080033) : undefined, x00200013: this.x00200013, x00280008: this.x00280008, From 8930b0c24f09d262a7e5b646675b8583bc354413 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 16:07:20 +0800 Subject: [PATCH 143/365] feat(audit): Add audit logger implementation - Created a new file `auditMessage.loggerImpl.js` which contains the implementation for logging audit messages. - Added a new file `auditMessage.model.js` which defines the `AuditMessageModel` class that accepts a database model as a parameter and implements the `createMessage` method. - Updated `auditManager.js` to use the new `AuditMessageModel` class for getting the audit message model. - Updated `auditMessageFactory.js` to use a different model for getting the instance model. --- models/DICOM/audit/auditManager.js | 5 ++-- models/DICOM/audit/auditMessageFactory.js | 4 +-- models/db/auditMessage.loggerImpl.js | 9 ++++++ models/db/auditMessage.model.js | 11 +++++++ models/sql/models/instance.model.js | 35 +++++++++++++++++++++++ 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 models/db/auditMessage.loggerImpl.js create mode 100644 models/db/auditMessage.model.js diff --git a/models/DICOM/audit/auditManager.js b/models/DICOM/audit/auditManager.js index b89b8596..a95bf4c4 100644 --- a/models/DICOM/audit/auditManager.js +++ b/models/DICOM/audit/auditManager.js @@ -2,6 +2,8 @@ const _ = require("lodash"); const { AuditMessageFactory } = require("./auditMessageFactory"); const { EventType } = require("./eventType"); +const { AuditMessageModel } = require("@models/db/auditMessage.model"); +const { AuditMessageModelLoggerDbImpl } = require("@models/db/auditMessage.loggerImpl"); /** * @typedef AuditMessageModel @@ -159,8 +161,7 @@ class AuditManager { } static getAuditMessageModel() { - const mongoose = require("mongoose"); - return mongoose.model("auditMessage"); + return new AuditMessageModel(new AuditMessageModelLoggerDbImpl()); } } diff --git a/models/DICOM/audit/auditMessageFactory.js b/models/DICOM/audit/auditMessageFactory.js index d9a565ea..f370c347 100644 --- a/models/DICOM/audit/auditMessageFactory.js +++ b/models/DICOM/audit/auditMessageFactory.js @@ -349,8 +349,8 @@ class AuditMessageFactory { } getInstanceModel() { - const mongoose = require("mongoose"); - return mongoose.model("dicom"); + const sequelizeInstance = require("@models/sql/instance"); + return sequelizeInstance.model("Instance"); } } diff --git a/models/db/auditMessage.loggerImpl.js b/models/db/auditMessage.loggerImpl.js new file mode 100644 index 00000000..3c41e79f --- /dev/null +++ b/models/db/auditMessage.loggerImpl.js @@ -0,0 +1,9 @@ +const { logger } = require("@root/utils/logs/log"); + +class AuditMessageModelLoggerDbImpl { + createMessage(msg) { + logger.info(JSON.stringify(msg)); + } +} + +module.exports.AuditMessageModelLoggerDbImpl = AuditMessageModelLoggerDbImpl; \ No newline at end of file diff --git a/models/db/auditMessage.model.js b/models/db/auditMessage.model.js new file mode 100644 index 00000000..53b64e68 --- /dev/null +++ b/models/db/auditMessage.model.js @@ -0,0 +1,11 @@ +class AuditMessageModel { + constructor(dbModel) { + this.dbModel = dbModel; + } + async createMessage(msg) { + return await this.dbModel.createMessage(msg); + } +} + + +module.exports.AuditMessageModel = AuditMessageModel; \ No newline at end of file diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index e2ab3fa0..3783a8a8 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -31,6 +31,41 @@ class InstanceModel extends Model { recursive: true }); } + + /** + * + * @param {string} studyUID + */ + static async getAuditInstancesInfoFromStudyUID(studyUID) { + let instances = await sequelizeInstance.model("Instance").findAll({ + where: { + x0020000D: studyUID + } + }); + + let instanceInfos = { + sopClassUIDs: [], + accessionNumbers: [], + patientID: "", + patientName: "" + }; + + for (let instance of instances) { + let sopClassUID = instance.x00080016; + let accessionNumber = instance.x00080050; + let patientID = instance.x00100020; + let patientName = _.get(instance.json, "00100010.Value.0.Alphabetic"); + sopClassUID ? instanceInfos.sopClassUIDs.push(sopClassUID) : null; + accessionNumber ? instanceInfos.accessionNumbers.push(accessionNumber) : null; + patientID ? instanceInfos.patientID = patientID : null; + patientName ? instanceInfos.patientName = patientName : null; + } + + instanceInfos.sopClassUIDs = _.uniq(instanceInfos.sopClassUIDs); + instanceInfos.accessionNumbers = _.uniq(instanceInfos.accessionNumbers); + + return instanceInfos; + } }; InstanceModel.init({ From a946c2df2365494a38725f1df60f33ebfa9ab90b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 16:44:55 +0800 Subject: [PATCH 144/365] feat: Update SQL configuration to include a logging option Previously, the SQL configuration only included database, dialect, username, and password options. This commit adds a new logging option to the configuration, allowing logging to be enabled or disabled. Also, update the database initialization code to include the logging option from the SQL configuration. This ensures that the logging setting is applied when connecting to the database. --- config-class.js | 3 ++- models/sql/init.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config-class.js b/config-class.js index 91df4fb9..9d98b500 100644 --- a/config-class.js +++ b/config-class.js @@ -33,7 +33,8 @@ const SqlDbConfig = { database: env.get("SQL_DB").default("raccoon").asString(), dialect: env.get("SQL_TYPE").default("postgres").asString(), username: env.get("SQL_USERNAME").default("postgres").asString(), - password: env.get("SQL_PASSWORD").default("postgres").asString() + password: env.get("SQL_PASSWORD").default("postgres").asString(), + logging: env.get("SQL_LOGGING").default("false").asBool() }; diff --git a/models/sql/init.js b/models/sql/init.js index f37ea89a..a37caa8d 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -19,7 +19,8 @@ async function initDatabasePostgres() { password: raccoonConfig.sqlDbConfig.password, host: raccoonConfig.sqlDbConfig.host, port: raccoonConfig.sqlDbConfig.port, - database: "postgres" + database: "postgres", + logging: raccoonConfig.sqlDbConfig.logging }); await client.connect(); From b27935c137c2f46d17723aec43a17bb662433edd Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 16:49:33 +0800 Subject: [PATCH 145/365] refactor: refactor code for creating and updating person names - The changes include updating the `createPersonName` method in `PersonNameModel` and refactoring the code in `VerifyingObserverSqPersistentObject`, `PatientPersistentObject`, `SeriesPersistentObject`, and `StudyPersistentObject` classes. - The `createPersonName` method now takes a `nameObj` parameter and returns the created person name. - The code in the mentioned classes that creates or updates person names now invokes the `createPersonName` and `updatePersonNameById` methods from the `PersonNameModel` class. - This refactoring improves code reusability and readability. --- models/sql/models/personName.model.js | 65 ++++++++++++++++++++++++++- models/sql/po/instance.po.js | 39 +++++----------- models/sql/po/patient.po.js | 26 +++-------- models/sql/po/series.po.js | 28 ++---------- models/sql/po/study.po.js | 21 +-------- 5 files changed, 88 insertions(+), 91 deletions(-) diff --git a/models/sql/models/personName.model.js b/models/sql/models/personName.model.js index 2ed7e9af..9e0be69b 100644 --- a/models/sql/models/personName.model.js +++ b/models/sql/models/personName.model.js @@ -1,8 +1,71 @@ const { Sequelize, DataTypes, Model } = require("sequelize"); const sequelizeInstance = require("@models/sql/instance"); +const { get } = require("lodash"); +class PersonNameModel extends Model { -class PersonNameModel extends Model {} + /** + * + * @param {any} nameObj + * @returns + */ + static async createPersonName(nameObj) { + if (!PersonNameModel.isEmpty(nameObj)) { + return await PersonNameModel.create({ + alphabetic: get(nameObj, "Alphabetic", undefined), + ideographic: get(nameObj, "Ideographic", undefined), + phonetic: get(nameObj, "Phonetic", undefined) + }); + } + return undefined; + } + + /** + * + * @param {any} nameObj + * @param {string} id + * @returns + */ + static async updatePersonNameById(nameObj, id) { + if (!PersonNameModel.isEmpty(nameObj)) { + return await PersonNameModel.update({ + alphabetic: get(nameObj, "Alphabetic", undefined), + ideographic: get(nameObj, "Ideographic", undefined), + phonetic: get(nameObj, "Phonetic", undefined) + }, { + where: { + id: id + } + }); + } + return undefined; + } + + /** + * + * @param {any} item + * @param {string} field + * @returns + */ + static async createPersonNames(item, field) { + let personNames = []; + if (item[field]) { + for (let personName of item[field]) { + let personNameSequelize = await PersonNameModel.create({ + alphabetic: get(personName, "Alphabetic", undefined), + ideographic: get(personName, "Ideographic", undefined), + phonetic: get(personName, "Phonetic", undefined) + }); + personNames.push(personNameSequelize); + } + } + return personNames; + } + + static isEmpty(json) { + return !json && !(json?.Alphabetic || json?.Ideographic || json?.Phonetic); + } +} PersonNameModel.init({ id: { type: DataTypes.INTEGER, diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js index 3e899e53..2609e969 100644 --- a/models/sql/po/instance.po.js +++ b/models/sql/po/instance.po.js @@ -424,48 +424,33 @@ class VerifyingObserverSqPersistentObject { */ async createName() { let nameItem = _.get(this.verifyingObserverSq, "0040A075.Value.0"); - if (nameItem && Object.values(nameItem).some(v => v)) { - return await PersonNameModel.create({ - alphabetic: _.get(nameItem, "Alphabetic", undefined), - ideographic: _.get(nameItem, "Ideographic", undefined), - phonetic: _.get(nameItem, "Phonetic", undefined) - }); - } - return undefined; + return await PersonNameModel.createPersonName(nameItem); } async updateName() { let verifyingObserverSq = await this.getExistItem(); + /** @type { PersonNameModel | undefined } */ let name = await verifyingObserverSq.getPersonName(); let nameItem = _.get(this.verifyingObserverSq, "0040A075.Value.0"); if (name) { - if (nameItem && Object.values(nameItem).some(v => v)) { - await PersonNameModel.update({ - alphabetic: _.get(nameItem, "Alphabetic", undefined), - ideographic: _.get(nameItem, "Ideographic", undefined), - phonetic: _.get(nameItem, "Phonetic", undefined) + let updatedName = await PersonNameModel.updatePersonNameById(nameItem, name.getDataValue("id")); + if (!updatedName) { + await VerifyIngObserverSqModel.update({ + x0040A075: null }, { + where: { + SOPInstanceUID: this.instance.dataValues.x00080018 + } + }); + await PersonNameModel.destroy({ where: { id: name.dataValues.id } }); + } else { return name; } - - await VerifyIngObserverSqModel.update({ - x0040A075: null - }, { - where: { - SOPInstanceUID: this.instance.dataValues.x00080018 - } - }); - await PersonNameModel.destroy({ - where: { - id: name.dataValues.id - } - }); - } else { return await this.createName(); } diff --git a/models/sql/po/patient.po.js b/models/sql/po/patient.po.js index 2246c5e0..67412da0 100644 --- a/models/sql/po/patient.po.js +++ b/models/sql/po/patient.po.js @@ -25,28 +25,16 @@ class PatientPersistentObject { } async createPersonName() { - if (this.x00100010) { - return await PersonNameModel.create({ - alphabetic: _.get(this.x00100010, "Alphabetic", undefined), - ideographic: _.get(this.x00100010, "Ideographic", undefined), - phonetic: _.get(this.x00100010, "Phonetic", undefined) - }); - } - return undefined; + return await PersonNameModel.createPersonName(this.x00100010); } + /** + * + * @param {PatientModel} patient + * @returns + */ async updatePersonName(patient) { - if (this.x00100010) { - await PersonNameModel.update({ - alphabetic: _.get(this.x00100010, "Alphabetic", undefined), - ideographic: _.get(this.x00100010, "Ideographic", undefined), - phonetic: _.get(this.x00100010, "Phonetic", undefined) - }, { - where: { - id: patient.dataValues.x00100010 - } - }); - } + return await PersonNameModel.updatePersonNameById(this.x00100010, patient.getDataValue("x00100010")); } async createPatient() { diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js index 5225f7c6..53b8a127 100644 --- a/models/sql/po/series.po.js +++ b/models/sql/po/series.po.js @@ -40,33 +40,11 @@ class SeriesPersistentObject { } async createReferringPhysicianName() { - if (this.x00080090) { - return await PersonNameModel.create({ - alphabetic: _.get(this.x00080090, "Alphabetic", undefined), - ideographic: _.get(this.x00080090, "Ideographic", undefined), - phonetic: _.get(this.x00080090, "Phonetic", undefined) - }); - } - return undefined; - } - - async createPersonNames(field) { - let personNames = []; - if (this[field]) { - for (let personName of this[field]) { - let personNameSequelize = await PersonNameModel.create({ - alphabetic: _.get(personName, "Alphabetic", undefined), - ideographic: _.get(personName, "Ideographic", undefined), - phonetic: _.get(personName, "Phonetic", undefined) - }); - personNames.push(personNameSequelize); - } - } - return personNames; + return await PersonNameModel.createPersonName(this.x00080090); } async addPerformingPhysicianNames(series) { - let performingPhysicianNames = await this.createPersonNames("x00081050"); + let performingPhysicianNames = await PersonNameModel.createPersonNames(series, "x00081050"); for (let performingPhysicianName of performingPhysicianNames) { await series.addPerformingPhysicianName(performingPhysicianName); } @@ -83,7 +61,7 @@ class SeriesPersistentObject { } async addOperatorsNames(series) { - let operationsNames = await this.createPersonNames("x00081070"); + let operationsNames = await PersonNameModel.createPersonNames(series, "x00081070"); for (let operationsName of operationsNames) { await series.addOperatorsName(operationsName); } diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js index c3b14c99..ef9f050a 100644 --- a/models/sql/po/study.po.js +++ b/models/sql/po/study.po.js @@ -31,28 +31,11 @@ class StudyPersistentObject { } async createReferringPhysicianName() { - if (this.x00080090) { - return await PersonNameModel.create({ - alphabetic: _.get(this.x00080090, "Alphabetic", undefined), - ideographic: _.get(this.x00080090, "Ideographic", undefined), - phonetic: _.get(this.x00080090, "Phonetic", undefined) - }); - } - return undefined; + return await PersonNameModel.createPersonName(this.x00080090); } async updateReferringPhysicianName(study) { - if (this.x00080090) { - await PersonNameModel.update({ - alphabetic: _.get(this.x00080090, "Alphabetic", undefined), - ideographic: _.get(this.x00080090, "Ideographic", undefined), - phonetic: _.get(this.x00080090, "Phonetic", undefined) - }, { - where: { - id: study.dataValues.x00080090 - } - }); - } + return await PersonNameModel.updatePersonNameById(this.x00080090, study.getDataValue("x00080090")); } async createStudy() { From 03044a69dabdaeb39b8117ae9a8a69a0ced81ca0 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 16:54:19 +0800 Subject: [PATCH 146/365] refactor(dicom-web): remove dicom-jpeg-generator of sql version - This commit removes the dicom-jpeg-generator file and its associated - Use original `dicom-jpeg-generator` and - replacing DicomToJpegTaskModel with module-alias --- .../STOW-RS/service/dicom-jpeg-generator.js | 77 ------------------- .../STOW-RS/service/stow-rs.service.js | 2 +- 2 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js diff --git a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js deleted file mode 100644 index 7515883c..00000000 --- a/api-sql/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js +++ /dev/null @@ -1,77 +0,0 @@ -const fs = require("fs"); -const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); -const colorette = require("colorette"); -const { DicomJpegGenerator } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator"); -const { DicomToJpegTaskModel } = require("@models/sql/models/dicomToJpegTask.model"); -/** - * @typedef JsDcm2JpegTask - * @property {Dcm2JpgExecutor$Dcm2JpgOptions} jsDcm2Jpeg - * @property {string} jpegFilename - */ - -class SqlDicomJpegGenerator extends DicomJpegGenerator { - /** - * - * @param {import("../../../../../models/DICOM/dicom-json-model").DicomJsonModel} dicomJsonModel - * @param {string} dicomInstanceFilename - */ - constructor(dicomJsonModel, dicomInstanceFilename) { - super(dicomJsonModel, dicomInstanceFilename); - } - - - - /** - * @private - */ - async insertStartTask_() { - let startTaskObj = { - studyUID: this.dicomJsonModel.uidObj.studyUID, - seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, - status: false, - message: "processing", - taskTime: new Date(), - finishedTime: null, - fileSize: `${(fs.statSync(this.dicomInstanceFilename).size / 1024 / 1024).toFixed(3)}MB` - }; - - await DicomToJpegTaskModel.insertOrUpdate(startTaskObj); - } - - /** - * @private - */ - async insertEndTask_() { - let endTaskObj = { - studyUID: this.dicomJsonModel.uidObj.studyUID, - seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, - status: true, - message: "generated", - finishedTime: new Date() - }; - - await DicomToJpegTaskModel.insertOrUpdate(endTaskObj); - } - - /** - * @private - * @param {string} message - */ - async insertErrorTask_(message) { - let errorTaskObj = { - studyUID: this.dicomJsonModel.uidObj.studyUID, - seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, - status: false, - message: message, - finishedTime: new Date() - }; - - await DicomToJpegTaskModel.insertOrUpdate(errorTaskObj); - } - -} - -module.exports.SqlDicomJpegGenerator = SqlDicomJpegGenerator; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js index 4ad9d694..1d926df7 100644 --- a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -4,7 +4,7 @@ const { DicomJsonParser } = require("@models/DICOM/dicom-json-parser"); const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); const { DicomFileSaver } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-file-saver"); const { SqlDicomJsonModel: DicomJsonModel, SqlDicomJsonBinaryDataModel: DicomJsonBinaryDataModel } = require("@models/sql/dicom-json-model"); -const { SqlDicomJpegGenerator: DicomJpegGenerator } = require("./dicom-jpeg-generator"); +const { DicomJpegGenerator } = require("@root/api/dicom-web/controller//STOW-RS/service/dicom-jpeg-generator"); class SqlStowRsService extends StowRsService { /** From 6d8d9765fa078062ed28fec12b1ae2704a83d25a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 16:58:17 +0800 Subject: [PATCH 147/365] refactor: Remove duplicate code and update import statements in STOW-RS controller files - Removes the duplicate code of "request-multipart-parser.js" file and - updates the import statements in the "storeInstance.js" file in the STOW-RS controller. --- .../service/request-multipart-parser.js | 47 ------------------- .../controller/STOW-RS/storeInstance.js | 3 +- 2 files changed, 1 insertion(+), 49 deletions(-) delete mode 100644 api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js diff --git a/api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js b/api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js deleted file mode 100644 index c10c8af3..00000000 --- a/api-sql/dicom-web/controller/STOW-RS/service/request-multipart-parser.js +++ /dev/null @@ -1,47 +0,0 @@ -const formidable = require("formidable"); -const path = require("path"); -const _ = require("lodash"); - -class StowRsRequestMultipartParser { - /** - * @param {import('express').Request} req - */ - constructor(req) { - this.request = req; - } - - /** - * - * @return {Promise} - */ - async parse() { - return new Promise((resolve, reject) => { - new formidable.IncomingForm({ - uploadDir: path.join(process.cwd(), "/tempUploadFiles"), - maxFileSize: 100 * 1024 * 1024 * 1024, - multiples: true, - isGetBoundaryInData: true - }).parse(this.request, async (err, fields, files) => { - - if (err) { - return reject(err); - } - - let fileField = Object.keys(files).pop(); - let uploadFiles = files[fileField]; - if (!_.isArray(uploadFiles)) uploadFiles = [uploadFiles]; - - return resolve({ - status: true, - multipart: { - fields: fields, - files: uploadFiles - } - }); - - }); - }); - } -} - -module.exports.StowRsRequestMultipartParser = StowRsRequestMultipartParser; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/STOW-RS/storeInstance.js b/api-sql/dicom-web/controller/STOW-RS/storeInstance.js index 80389a74..e3be32ce 100644 --- a/api-sql/dicom-web/controller/STOW-RS/storeInstance.js +++ b/api-sql/dicom-web/controller/STOW-RS/storeInstance.js @@ -1,8 +1,7 @@ const { performance } = require("node:perf_hooks"); -const errorResponseMessage = require("@root/utils/errorResponse/errorResponseMessage"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { Controller } = require("@root/api/controller.class"); -const { StowRsRequestMultipartParser } = require("./service/request-multipart-parser"); +const { StowRsRequestMultipartParser } = require("@root/api/dicom-web/controller/STOW-RS/service/request-multipart-parser"); const { SqlStowRsService: StowRsService } = require("./service/stow-rs.service"); class StoreInstanceController extends Controller { From 61a391c5fc3bf1e724bdab5d5daf8953c86d9cde Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 17:03:30 +0800 Subject: [PATCH 148/365] feat: add fns to get tag, keyword and vr of dicom json --- models/DICOM/dicom-json-model.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 9359fd3f..219019c6 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -80,6 +80,18 @@ class BaseDicomJson { _.set(this.dicomJson, `${tag}.vr`, vrOfTag); _.set(this.dicomJson, `${tag}.Value`, [value]); } + + static getKeywordOfTag(tag) { + return _.get(dictionary.tag, `${tag}`); + } + + static getTagOfKeyword(keyword) { + return _.get(dictionary.keyword, `${keyword}`); + } + + static getTagVrOfTag(tag) { + return _.get(dictionary.tagVR, `${tag}.vr`); + } } class DicomJsonModel { From 9504545c10e68996d811deec9cda8715d0ece0e6 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 17:19:59 +0800 Subject: [PATCH 149/365] refactor: Update DicomJsonBinaryDataModel constructor and use a dynamic bulkDataModelClass - Updated DicomJsonBinaryDataModel constructor to accept an optional bulkDataModelClass parameter. - Added a property bulkDataModelClass to store the passed class. - Refactored the code to use the dynamic bulkDataModelClass to create bulkData objects. - Updated the creation and storing of bulkData objects to use the dynamic bulkDataModelClass. --- models/DICOM/dicom-json-model.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 219019c6..c21250c3 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -422,7 +422,7 @@ class DicomJsonModel { class DicomJsonBinaryDataModel { - constructor(dicomJsonModel) { + constructor(dicomJsonModel, bulkDataModelClass=BulkData) { /** @type {DicomJsonModel} */ this.dicomJsonModel = dicomJsonModel; @@ -432,6 +432,8 @@ class DicomJsonBinaryDataModel { /** @type {string[]} */ this.pathGroupOfBinaryProperties = this.getPathGroupOfBinaryProperties_(); + + this.bulkDataModelClass = bulkDataModelClass; } getBinaryKeys_() { @@ -528,7 +530,7 @@ class DicomJsonBinaryDataModel { await fsP.writeFile(filename, Buffer.from(binaryData, "base64")); logger.info(`[STOW-RS] [Store binary data to ${filename}]`); - let bulkData = new BulkData(this.dicomJsonModel.uidObj, relativeFilename, pathOfBinaryProperty); + let bulkData = new this.bulkDataModelClass(this.dicomJsonModel.uidObj, relativeFilename, pathOfBinaryProperty); await bulkData.storeToDb(); } From 484c31ffb8748ad8a208c7b96f1ba1c03a4dd5d9 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 17:37:29 +0800 Subject: [PATCH 150/365] refactor: Remove redundant code in SqlDicomJsonModel and SqlDicomJsonBinaryDataModel classes --- .../STOW-RS/service/stow-rs.service.js | 92 ------------ .../controller/STOW-RS/storeInstance.js | 68 --------- dimse-sql/c-store.js | 2 +- models/sql/dicom-json-model.js | 142 ++++++------------ routes.js | 2 +- 5 files changed, 50 insertions(+), 256 deletions(-) delete mode 100644 api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js delete mode 100644 api-sql/dicom-web/controller/STOW-RS/storeInstance.js diff --git a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js deleted file mode 100644 index 1d926df7..00000000 --- a/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ /dev/null @@ -1,92 +0,0 @@ -const _ = require("lodash"); - -const { DicomJsonParser } = require("@models/DICOM/dicom-json-parser"); -const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); -const { DicomFileSaver } = require("@root/api/dicom-web/controller/STOW-RS/service/dicom-file-saver"); -const { SqlDicomJsonModel: DicomJsonModel, SqlDicomJsonBinaryDataModel: DicomJsonBinaryDataModel } = require("@models/sql/dicom-json-model"); -const { DicomJpegGenerator } = require("@root/api/dicom-web/controller//STOW-RS/service/dicom-jpeg-generator"); - -class SqlStowRsService extends StowRsService { - /** - * @param {import('express').Request} req - * @param {import('formidable').File[]} uploadFiles - */ - constructor(req, uploadFiles) { - super(req, uploadFiles); - } - - async storeInstances() { - for (let i = 0; i < this.uploadFiles.length; i++) { - - let currentFile = this.uploadFiles[i]; - - let { - dicomJsonModel, - dicomFileSaveInfo - } = await this.storeInstance(currentFile); - - - //sync DICOM to FHIR - // if (isSyncToFhir) { - // let dicomFhirService = new DicomFhirService(this.request, dicomJsonModel); - // await dicomFhirService.initDicomFhirConverter(); - // await dicomFhirService.postDicomToFhirServerAndStoreLog(); - // } - - //generate JPEG - let dicomJpegGenerator = new DicomJpegGenerator(dicomJsonModel, dicomFileSaveInfo.instancePath); - dicomJpegGenerator.generateAllFrames(); - } - - return { - code: this.responseCode, - responseMessage: this.responseMessage - }; - } - - /** - * - * @param {import("formidable").File} file - */ - async storeInstance(file) { - let dicomJsonParser = new DicomJsonParser(); - let dicomJson = await dicomJsonParser.parseFromFilename(file.filepath); - - let dicomJsonModel = new DicomJsonModel(dicomJson); - dicomJsonModel.setMinifyDicomJsonAndTempBigValueTags(); - dicomJsonModel.setUidObj(); - - let isSameStudyIDStatus = this.isSameStudyID_(this.responseMessage); - if (!isSameStudyIDStatus) { - this.responseCode = 409; - } - - let dicomJsonBinaryDataModel = new DicomJsonBinaryDataModel(dicomJsonModel); - await dicomJsonBinaryDataModel.storeAllBinaryDataToFileAndDb(); - dicomJsonBinaryDataModel.replaceAllBinaryToURI(); - - let dicomFileSaver = new DicomFileSaver(file, dicomJsonModel); - let dicomFileSaveInfo = await dicomFileSaver.saveAndGetSaveInfo(); - - await dicomJsonModel.saveMetadataToFile(dicomFileSaveInfo.fullPath); - - await dicomJsonModel.storeToDb(dicomFileSaveInfo); - - let retrieveUrlObj = this.getRetrieveUrl(dicomJsonModel.uidObj); - this.responseMessage["00081190"].Value.push(retrieveUrlObj.study); - this.responseMessage["00081190"].Value = _.uniq(this.responseMessage["00081190"].Value); - - let sopSeq = this.getSOPSeq(dicomJsonModel.uidObj.sopClass, dicomJsonModel.uidObj.sopInstanceUID); - _.set(sopSeq, "00081190.vr", "UT"); - _.set(sopSeq, "00081190.Value", [retrieveUrlObj.instance]); - this.responseMessage["00081199"]["Value"].push(sopSeq); - - return { - dicomJsonModel, - dicomFileSaveInfo - }; - } -} - - -module.exports.SqlStowRsService = SqlStowRsService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/STOW-RS/storeInstance.js b/api-sql/dicom-web/controller/STOW-RS/storeInstance.js deleted file mode 100644 index e3be32ce..00000000 --- a/api-sql/dicom-web/controller/STOW-RS/storeInstance.js +++ /dev/null @@ -1,68 +0,0 @@ -const { performance } = require("node:perf_hooks"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); -const { StowRsRequestMultipartParser } = require("@root/api/dicom-web/controller/STOW-RS/service/request-multipart-parser"); -const { SqlStowRsService: StowRsService } = require("./service/stow-rs.service"); - -class StoreInstanceController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let startSTOWTime = performance.now(); - let retCode; - let storeMessage; - let apiLogger = new ApiLogger(this.request, "STOW-RS"); - apiLogger.addTokenValue(); - - try { - let requestMultipartParser = new StowRsRequestMultipartParser(this.request); - let multipartParseResult = await requestMultipartParser.parse(); - - if (multipartParseResult.status) { - let stowRsService = new StowRsService(this.request, multipartParseResult.multipart.files); - let storeInstancesResult = await stowRsService.storeInstances(); - - retCode = storeInstancesResult.code; - storeMessage = storeInstancesResult.responseMessage; - } - let endSTOWTime = performance.now(); - let elapsedTime = (endSTOWTime - startSTOWTime).toFixed(3); - apiLogger.logger.info(`Finished STOW-RS, elapsed time: ${elapsedTime} ms`); - - this.response.writeHead(retCode, { - "Content-Type": "application/dicom" - }); - - return this.response.end(JSON.stringify(storeMessage)); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} - - -/** - * To store DICOM instance - * 1. we parse multipart request to get file info that user upload - * 2. parse DICOM to JSON and store DICOM file from step 1 - * 3. parse DICOM json model to FHIR (Patient, Endpoint, ImagingStudy) - * 4. upload FHIR to FHIR server - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new StoreInstanceController(req, res); - - await controller.doPipeline(); -}; diff --git a/dimse-sql/c-store.js b/dimse-sql/c-store.js index faea344e..bb8c1195 100644 --- a/dimse-sql/c-store.js +++ b/dimse-sql/c-store.js @@ -2,7 +2,7 @@ const path = require("path"); const { createCStoreSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CStoreSCPInject"); const { default: SimpleCStoreSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCStoreSCP"); const { default: File } = require("@java-wrapper/java/io/File"); -const { SqlStowRsService: StowRsService } = require("@root/api-sql/dicom-web/controller/STOW-RS/service/stow-rs.service"); +const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); const cStoreScpInjectProxy = createCStoreSCPInjectProxy({ postDimseRQ: async (association, presentationContext, dimse, requestAttr, data, responseAttr) => { diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index 49a67755..444d3ae5 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -15,105 +15,59 @@ const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); const { raccoonConfig } = require("@root/config-class"); const { logger } = require("@root/utils/logs/log"); +DicomJsonModel.prototype.storeToDb = async function (dicomFileSaveInfo) { + let dicomJsonClone = _.cloneDeep(this.dicomJson); + try { + let mediaStorage = this.getMediaStorageInfo(); + _.merge(dicomJsonClone, this.uidObj); + _.merge(dicomJsonClone, { + studyPath: dicomFileSaveInfo.studyPath, + seriesPath: dicomFileSaveInfo.seriesPath, + instancePath: dicomFileSaveInfo.relativePath + }); + _.merge(dicomJsonClone, mediaStorage); -class SqlDicomJsonModel extends DicomJsonModel { - constructor(dicomJson) { - super(dicomJson); - } - - async storeToDb(dicomFileSaveInfo) { - let dicomJsonClone = _.cloneDeep(this.dicomJson); - try { - let mediaStorage = this.getMediaStorageInfo(); - _.merge(dicomJsonClone, this.uidObj); - _.merge(dicomJsonClone, { - studyPath: dicomFileSaveInfo.studyPath, - seriesPath: dicomFileSaveInfo.seriesPath, - instancePath: dicomFileSaveInfo.relativePath - }); - _.merge(dicomJsonClone, mediaStorage); - - delete dicomJsonClone.sopClass; - delete dicomJsonClone.sopInstanceUID; - - let storedPatient = await this.storePatientCollection(dicomJsonClone); - let storedStudy = await this.storeStudyCollection(dicomJsonClone, storedPatient); - let storedSeries = await this.storeSeriesCollection(dicomJsonClone, storedStudy); - await this.storeInstanceCollection(dicomJsonClone, storedSeries); - - await StudyModel.updateModalitiesInStudy(storedStudy); - } catch(e) { - throw e; - } - } - - async storePatientCollection(dicomJson) { - let patientPo = new PatientPersistentObject(dicomJson); - let patient = await patientPo.createPatient(); - return patient; - } - - async storeStudyCollection(dicomJson, patient) { - let studyPo = new StudyPersistentObject(dicomJson, patient); - let study = await studyPo.createStudy(); - return study; - } + delete dicomJsonClone.sopClass; + delete dicomJsonClone.sopInstanceUID; - async storeSeriesCollection(dicomJson, study) { - let seriesPo = new SeriesPersistentObject(dicomJson, study); - let series = await seriesPo.createSeries(); - return series; - } + let storedPatient = await this.storePatientCollection(dicomJsonClone); + let storedStudy = await this.storeStudyCollection(dicomJsonClone, storedPatient); + let storedSeries = await this.storeSeriesCollection(dicomJsonClone, storedStudy); + await this.storeInstanceCollection(dicomJsonClone, storedSeries); - async storeInstanceCollection(dicomJson, series) { - let instancePo = new InstancePersistentObject(dicomJson, series); - return await instancePo.createInstance(); + await StudyModel.updateModalitiesInStudy(storedStudy); + } catch (e) { + throw e; } -} - -class SqlDicomJsonBinaryDataModel extends DicomJsonBinaryDataModel { +}; +DicomJsonModel.prototype.storePatientCollection = async function (dicomJson) { + let patientPo = new PatientPersistentObject(dicomJson); + let patient = await patientPo.createPatient(); + return patient; +}; + +DicomJsonModel.prototype.storeStudyCollection = async function(dicomJson, patient) { + let studyPo = new StudyPersistentObject(dicomJson, patient); + let study = await studyPo.createStudy(); + return study; +}; + +DicomJsonModel.prototype.storeSeriesCollection = async function (dicomJson, study) { + let seriesPo = new SeriesPersistentObject(dicomJson, study); + let series = await seriesPo.createSeries(); + return series; +}; + +DicomJsonModel.prototype.storeInstanceCollection = async function(dicomJson, series) { + let instancePo = new InstancePersistentObject(dicomJson, series); + return await instancePo.createInstance(); +}; + +class SqlDicomJsonBinaryDataModel extends DicomJsonBinaryDataModel{ constructor(dicomJsonModel) { super(dicomJsonModel); + this.bulkDataModelClass = BulkData; } - - async storeAllBinaryDataToFileAndDb() { - let { - sopInstanceUID - } = this.dicomJsonModel.uidObj; - - let shortInstanceUID = shortHash(sopInstanceUID); - - - for(let i = 0; i < this.pathGroupOfBinaryProperties.length ; i++) { - let relativeFilename = `files/bulkData/${shortInstanceUID}/`; - let pathOfBinaryProperty = this.pathGroupOfBinaryProperties[i]; - - let binaryData = _.get(this.dicomJsonModel.dicomJson, pathOfBinaryProperty); - - if(binaryData) { - relativeFilename += `${pathOfBinaryProperty}.raw`; - let filename = path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - relativeFilename - ); - - mkdirp.sync( - path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - `files/bulkData/${shortInstanceUID}` - ) - ); - - await fsP.writeFile(filename, Buffer.from(binaryData, "base64")); - logger.info(`[STOW-RS] [Store binary data to ${filename}]`); - - let bulkData = new BulkData(this.dicomJsonModel.uidObj, relativeFilename, pathOfBinaryProperty); - await bulkData.storeToDb(); - } - - } - } - } class BulkData { @@ -146,5 +100,5 @@ class BulkData { } } -module.exports.SqlDicomJsonModel = SqlDicomJsonModel; -module.exports.SqlDicomJsonBinaryDataModel = SqlDicomJsonBinaryDataModel; \ No newline at end of file +module.exports.DicomJsonModel = DicomJsonModel; +module.exports.DicomJsonBinaryDataModel = SqlDicomJsonBinaryDataModel; \ No newline at end of file diff --git a/routes.js b/routes.js index 83ec2586..437d2d00 100644 --- a/routes.js +++ b/routes.js @@ -19,7 +19,7 @@ module.exports = function (app) { loadAllPlugin(); - app.use("/dicom-web", require("./api-sql/dicom-web/stow-rs.route")); + app.use("/dicom-web", require("./api/dicom-web/stow-rs.route")); app.use("/dicom-web", require("./api-sql/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); From 6ce59189777692477cc4c2f5c8c729669738f2ea Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 17:48:47 +0800 Subject: [PATCH 151/365] refactor: extract logic for cleaning data before storing to the database - Extracted the logic for cleaning data before storing to the database into a separate method called getCleanDataBeforeStoringToDb. - Updated storeToDb method to call getCleanDataBeforeStoringToDb to get the cleaned data. - Refactored the code to use the cleaned data for storing in the database. --- models/DICOM/dicom-json-model.js | 39 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index c21250c3..233bc8f9 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -149,32 +149,35 @@ class DicomJsonModel { } async storeToDb(dicomFileSaveInfo) { - let dicomJsonClone = _.cloneDeep(this.dicomJson); + let dbJson = this.getCleanDataBeforeStoringToDb(dicomFileSaveInfo); try { - let mediaStorage = this.getMediaStorageInfo(); - _.merge(dicomJsonClone, this.uidObj); - _.merge(dicomJsonClone, { - studyPath: dicomFileSaveInfo.studyPath, - seriesPath: dicomFileSaveInfo.seriesPath, - instancePath: dicomFileSaveInfo.relativePath - }); - _.merge(dicomJsonClone, mediaStorage); - _.set(dicomJsonClone, "deleteStatus", 0); - - delete dicomJsonClone.sopClass; - delete dicomJsonClone.sopInstanceUID; - await Promise.all([ - this.storeInstanceCollection(dicomJsonClone), - this.storeStudyCollection(dicomJsonClone), - this.storeSeriesCollection(dicomJsonClone), - this.storePatientCollection(dicomJsonClone) + this.storeInstanceCollection(dbJson), + this.storeStudyCollection(dbJson), + this.storeSeriesCollection(dbJson), + this.storePatientCollection(dbJson) ]); } catch(e) { throw e; } } + getCleanDataBeforeStoringToDb(dicomFileSaveInfo) { + let dicomJsonClone = _.cloneDeep(this.dicomJson); + let mediaStorage = this.getMediaStorageInfo(); + _.merge(dicomJsonClone, this.uidObj); + _.merge(dicomJsonClone, { + studyPath: dicomFileSaveInfo.studyPath, + seriesPath: dicomFileSaveInfo.seriesPath, + instancePath: dicomFileSaveInfo.relativePath + }); + _.merge(dicomJsonClone, mediaStorage); + + delete dicomJsonClone.sopClass; + delete dicomJsonClone.sopInstanceUID; + return dicomJsonClone; + } + async storeInstanceCollection(dicomJson) { let query = { $and: [ From 4e0534ab733340f4c1909132a95ba384fcffb059 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 17:50:54 +0800 Subject: [PATCH 152/365] refactor(sql): DicomJsonModel's storeToDb method to use clean data before storing to DB --- models/sql/dicom-json-model.js | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index 444d3ae5..f8c31f25 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -16,24 +16,13 @@ const { raccoonConfig } = require("@root/config-class"); const { logger } = require("@root/utils/logs/log"); DicomJsonModel.prototype.storeToDb = async function (dicomFileSaveInfo) { - let dicomJsonClone = _.cloneDeep(this.dicomJson); - try { - let mediaStorage = this.getMediaStorageInfo(); - _.merge(dicomJsonClone, this.uidObj); - _.merge(dicomJsonClone, { - studyPath: dicomFileSaveInfo.studyPath, - seriesPath: dicomFileSaveInfo.seriesPath, - instancePath: dicomFileSaveInfo.relativePath - }); - _.merge(dicomJsonClone, mediaStorage); + let dbJson = this.getCleanDataBeforeStoringToDb(dicomFileSaveInfo); - delete dicomJsonClone.sopClass; - delete dicomJsonClone.sopInstanceUID; - - let storedPatient = await this.storePatientCollection(dicomJsonClone); - let storedStudy = await this.storeStudyCollection(dicomJsonClone, storedPatient); - let storedSeries = await this.storeSeriesCollection(dicomJsonClone, storedStudy); - await this.storeInstanceCollection(dicomJsonClone, storedSeries); + try { + let storedPatient = await this.storePatientCollection(dbJson); + let storedStudy = await this.storeStudyCollection(dbJson, storedPatient); + let storedSeries = await this.storeSeriesCollection(dbJson, storedStudy); + await this.storeInstanceCollection(dbJson, storedSeries); await StudyModel.updateModalitiesInStudy(storedStudy); } catch (e) { From f20ee13ec4ad51179bc55dd5cfe3c95acc7ab9c0 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 18:08:05 +0800 Subject: [PATCH 153/365] refactor: Update import paths for QIDO-RS service (module-alias) This commit updates the import paths for the QIDO-RS service in order to use the modules-alias from the "@query-dicom-json-factory" package. --- api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js | 2 +- jsconfig.json | 3 ++- package.json | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index cc470810..38f02d49 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -8,7 +8,7 @@ const { const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); -const { QueryPatientDicomJsonFactory, QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("./query-dicom-json-factory"); +const { QueryPatientDicomJsonFactory, QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("@query-dicom-json-factory"); const HierarchyQueryDicomJsonFactory = Object.freeze({ patient: QueryPatientDicomJsonFactory, diff --git a/jsconfig.json b/jsconfig.json index 1151bfbb..9d8fade8 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -9,7 +9,8 @@ "@root/*": ["./*"], "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], "@dbModels/*": ["./models/mongodb/models/*"], - "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"] + "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"], + "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"] } }, "exclude": [ diff --git a/package.json b/package.json index e798d3a7..2733257d 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,8 @@ "@chinlinlee": "./models/DICOM/dcm4che/wrapper/org/github/chinlinlee", "@dbModels": "./models/mongodb/models", "@dbInitializer": "./models/mongodb/index.js", - "@dicom-json-model": "./models/DICOM/dicom-json-model.js" + "@dicom-json-model": "./models/DICOM/dicom-json-model.js", + "@query-json-factory": "./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js" }, "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", From 6ff6575a990d65c6393840e27fbf4bc9d33d895e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 19:38:37 +0800 Subject: [PATCH 154/365] refactor: database configuration classes - Add a new `MongoDbConfig` class with properties for MongoDB configuration. - Update the `ServerConfig` constructor to assign a `dbType` property. - Modify the `RaccoonConfig` constructor to conditionally assign the `dbConfig` property based on the `dbType`. - Update the `mediaStorageUID` and `mediaStorageID` properties in `RaccoonConfig` to use the `dbConfig` properties. - Modify the `vrTypeMapping` to use the `dbConfig` dialect property instead of `sqlDbConfig`. --- .../QIDO-RS/service/querybuilder.js | 2 +- .../QIDO-RS/service/seriesQueryBuilder.js | 2 +- config-class.js | 48 +++++++++++++------ models/sql/init.js | 18 +++---- models/sql/instance.js | 2 +- models/sql/vrTypeMapping.js | 2 +- 6 files changed, 46 insertions(+), 28 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 64aeb3d5..a8e9cc63 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -132,7 +132,7 @@ class BaseQueryBuilder { } getStringArrayJsonQuery(tag, value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + if (raccoonConfig.dbConfig.dialect === "postgres") { return { [`x${tag}`]: { [Op.contains]: cast(JSON.stringify([value]), "jsonb") diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js index dc4277d5..779a7692 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js @@ -60,7 +60,7 @@ class SeriesQueryBuilder extends BaseQueryBuilder { } getPersonNameJsonArrayQuery(tag, value) { - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + if (raccoonConfig.dbConfig.dialect === "postgres") { value = this.getWildCardRegexString(value); return { [Op.or]: [ diff --git a/config-class.js b/config-class.js index 9d98b500..3282ec07 100644 --- a/config-class.js +++ b/config-class.js @@ -24,25 +24,37 @@ function generateUidFromGuid(iGuid) { return `2.25.${bigInteger.toString()}`; //Output the previus parsed integer as string by adding `2.25.` as prefix } -/** - * @type {import("sequelize").Options} - */ -const SqlDbConfig = { - host: env.get("SQL_HOST").default("127.0.0.1").asString(), - port: env.get("SQL_PORT").default("5432").asString(), - database: env.get("SQL_DB").default("raccoon").asString(), - dialect: env.get("SQL_TYPE").default("postgres").asString(), - username: env.get("SQL_USERNAME").default("postgres").asString(), - password: env.get("SQL_PASSWORD").default("postgres").asString(), - logging: env.get("SQL_LOGGING").default("false").asBool() -}; +class SqlDbConfig { + constructor() { + this.host = env.get("SQL_HOST").default("127.0.0.1").asString(); + this.port = env.get("SQL_PORT").default("5432").asString(); + this.database = env.get("SQL_DB").default("raccoon").asString(); + this.dialect = env.get("SQL_TYPE").default("postgres").asString(); + this.username = env.get("SQL_USERNAME").default("postgres").asString(); + this.password = env.get("SQL_PASSWORD").default("postgres").asString(); + this.logging = env.get("SQL_LOGGING").default("false").asBool(); + } +} +class MongoDbConfig { + constructor() { + this.dbName = env.get("MONGODB_NAME").default("raccoon").asString(); + this.hosts = env.get("MONGODB_HOSTS").required().asJsonArray(); + this.ports = env.get("MONGODB_PORTS").required().asJsonArray(); + this.user = env.get("MONGODB_USER").default("").asString(); + this.password = env.get("MONGODB_PASSWORD").default("").asString(); + this.authSource = env.get("MONGODB_AUTH_SOURCE").default("admin").asString(); + this.urlOptions = env.get("MONGODB_OPTIONS").default("").asString(); + this.isShardingMode = env.get("MONGODB_IS_SHARDING_MODE").default("false").asBool(); + } +} class ServerConfig { constructor() { this.host = env.get("SERVER_HOST").default("127.0.0.1").asString(); this.port = env.get("SERVER_PORT").default("8081").asInt(); this.secretKey = env.get("SERVER_SESSION_SECRET_KEY").asString(); + this.dbType = env.get("SERVER_DB_TYPE").default("mongodb").asEnum(["mongodb", "sql"]); } } @@ -66,19 +78,25 @@ class FhirConfig { class RaccoonConfig { constructor() { - this.sqlDbConfig = SqlDbConfig; this.serverConfig = new ServerConfig(); + + if (this.serverConfig.dbType === "mongodb") { + this.dbConfig = new MongoDbConfig(); + } else if (this.serverConfig.dbType === "sql") { + this.dbConfig = new SqlDbConfig(); + } + this.dicomWebConfig = new DicomWebConfig(); this.dicomDimseConfig = new DimseConfig(); this.fhirConfig = new FhirConfig(); /** @type {string} */ this.mediaStorageUID = generateUidFromGuid( - uuid.v5(this.sqlDbConfig.database, NAME_SPACE) + uuid.v5(this.dbConfig.database, NAME_SPACE) ); /** @type {string} */ - this.mediaStorageID = this.sqlDbConfig.database; + this.mediaStorageID = this.dbConfig.database; this.aeTitle = this.dicomWebConfig.aeTitle; // this.aeTitle = this.dicomDimseConfig.enableDimse ? this.dicomDimseConfig.getAeTitle() : this.dicomWebConfig.aeTitle; diff --git a/models/sql/init.js b/models/sql/init.js index a37caa8d..36992be0 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -15,20 +15,20 @@ const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model async function initDatabasePostgres() { const { Client } = require("pg"); const client = new Client({ - user: raccoonConfig.sqlDbConfig.username, - password: raccoonConfig.sqlDbConfig.password, - host: raccoonConfig.sqlDbConfig.host, - port: raccoonConfig.sqlDbConfig.port, + user: raccoonConfig.dbConfig.username, + password: raccoonConfig.dbConfig.password, + host: raccoonConfig.dbConfig.host, + port: raccoonConfig.dbConfig.port, database: "postgres", - logging: raccoonConfig.sqlDbConfig.logging + logging: raccoonConfig.dbConfig.logging }); await client.connect(); try { - let result = await client.query(`SELECT 'CREATE DATABASE ${raccoonConfig.sqlDbConfig.database}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${raccoonConfig.sqlDbConfig.database}')`); + let result = await client.query(`SELECT 'CREATE DATABASE ${raccoonConfig.dbConfig.database}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${raccoonConfig.dbConfig.database}')`); if (result.rowCount > 0 ) { - await client.query(`CREATE DATABASE ${raccoonConfig.sqlDbConfig.database}`); + await client.query(`CREATE DATABASE ${raccoonConfig.dbConfig.database}`); } } catch(e) { console.error(e); @@ -41,7 +41,7 @@ async function initDatabasePostgres() { async function init() { require("./deleteSchedule"); - if (raccoonConfig.sqlDbConfig.dialect === "postgres") { + if (raccoonConfig.dbConfig.dialect === "postgres") { await initDatabasePostgres(); } @@ -130,7 +130,7 @@ async function init() { }); //TODO: 設計完畢後要將 force 刪除 - await sequelizeInstance.sync(); + await sequelizeInstance.sync({force: true}); } catch (e) { console.error('Unable to connect to the database:', e); process.exit(1); diff --git a/models/sql/instance.js b/models/sql/instance.js index 197eb0e7..e71e1d32 100644 --- a/models/sql/instance.js +++ b/models/sql/instance.js @@ -1,7 +1,7 @@ const { raccoonConfig } = require("@root/config-class"); const { Sequelize } = require("sequelize"); -const sequelize = new Sequelize(raccoonConfig.sqlDbConfig); +const sequelize = new Sequelize(raccoonConfig.dbConfig); /** * @type {Sequelize} diff --git a/models/sql/vrTypeMapping.js b/models/sql/vrTypeMapping.js index 71d7c544..e1627351 100644 --- a/models/sql/vrTypeMapping.js +++ b/models/sql/vrTypeMapping.js @@ -33,7 +33,7 @@ const vrTypeMapping = { "US": DataTypes.SMALLINT.UNSIGNED, "UT": DataTypes.TEXT("long"), "UV": DataTypes.BIGINT.UNSIGNED, - "JSON": raccoonConfig.sqlDbConfig.dialect === "postgres" ? DataTypes.JSONB : DataTypes.JSON // For Array or SQ data + "JSON": raccoonConfig.dbConfig.dialect === "postgres" ? DataTypes.JSONB : DataTypes.JSON // For Array or SQ data }; From 992fa2940d29781697bc6333045b5b3e174e9183 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 19:40:05 +0800 Subject: [PATCH 155/365] refactor: module-alias configuration for mongodb and sql config files - Added gitignore rules for /jsconfig.json files - **copy/paste config by your self** - Added module-alias configuration for jsconfig.mongodb.json and jsconfig.sql.json files - Added modula-alias configuration for mongodb and sql packages in package.json - Updated server.js to use modula-alias based on raccoon server config --- .gitignore | 4 +++- config/jsconfig.mongodb.json | 18 ++++++++++++++++++ jsconfig.json => config/jsconfig.sql.json | 3 ++- config/modula-alias/mongodb/package.json | 13 +++++++++++++ config/modula-alias/sql/package.json | 15 +++++++++++++++ package.json | 12 ------------ server.js | 10 ++++++++-- 7 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 config/jsconfig.mongodb.json rename jsconfig.json => config/jsconfig.sql.json (81%) create mode 100644 config/modula-alias/mongodb/package.json create mode 100644 config/modula-alias/sql/package.json diff --git a/.gitignore b/.gitignore index 069815de..9d34e050 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ config/ae-prod.properties # ignore jscpd output report -/report \ No newline at end of file +/report + +/jsconfig.json \ No newline at end of file diff --git a/config/jsconfig.mongodb.json b/config/jsconfig.mongodb.json new file mode 100644 index 00000000..1151bfbb --- /dev/null +++ b/config/jsconfig.mongodb.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "paths": { + "@dcm4che/*": ["./models/DICOM/dcm4che/wrapper/org/dcm4che3/*"], + "@java-wrapper/*": ["./models/DICOM/dcm4che/wrapper/*"], + "@models/*": ["./models/*"], + "@error/*" : ["./error/*"], + "@root/*": ["./*"], + "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], + "@dbModels/*": ["./models/mongodb/models/*"], + "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"] + } + }, + "exclude": [ + "node_modules", "**/node_modules/*" + ] +} \ No newline at end of file diff --git a/jsconfig.json b/config/jsconfig.sql.json similarity index 81% rename from jsconfig.json rename to config/jsconfig.sql.json index 379dea27..19a55116 100644 --- a/jsconfig.json +++ b/config/jsconfig.sql.json @@ -10,7 +10,8 @@ "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], "@dbModels/*": ["./models/sql/models/*"], "@dicom-json-model": ["./models/sql/dicom-json-model.js"], - "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"] + "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"], + "@qido-rs-service": ["./api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], } }, "exclude": [ diff --git a/config/modula-alias/mongodb/package.json b/config/modula-alias/mongodb/package.json new file mode 100644 index 00000000..a14c6209 --- /dev/null +++ b/config/modula-alias/mongodb/package.json @@ -0,0 +1,13 @@ +{ + "_moduleAliases": { + "@dcm4che": "../../../models/DICOM/dcm4che/wrapper/org/dcm4che3", + "@java-wrapper": "../../../models/DICOM/dcm4che/wrapper", + "@models": "../../../models", + "@error": "../../../error", + "@root": "../../../", + "@chinlinlee": "../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee", + "@dbModels": "../../../models/mongodb/models", + "@dbInitializer": "../../../models/mongodb/index.js", + "@dicom-json-model": "../../../models/DICOM/dicom-json-model.js" + } +} \ No newline at end of file diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json new file mode 100644 index 00000000..76e89807 --- /dev/null +++ b/config/modula-alias/sql/package.json @@ -0,0 +1,15 @@ +{ + "_moduleAliases": { + "@dcm4che": "../../../models/DICOM/dcm4che/wrapper/org/dcm4che3", + "@java-wrapper": "../../../models/DICOM/dcm4che/wrapper", + "@models": "../../../models", + "@error": "../../../error", + "@root": "../../../", + "@chinlinlee": "../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee", + "@dbModels": "../../../models/sql/models", + "@dbInitializer": "../../../models/sql/initializer.js", + "@dicom-json-model": "../../../models/sql/dicom-json-model.js", + "@query-dicom-json-factory": "../../../api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js", + "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 114d0d90..bfef97f7 100644 --- a/package.json +++ b/package.json @@ -54,18 +54,6 @@ "keywords": [], "author": "chinlinlee", "license": "MIT", - "_moduleAliases": { - "@dcm4che": "./models/DICOM/dcm4che/wrapper/org/dcm4che3", - "@java-wrapper": "./models/DICOM/dcm4che/wrapper", - "@models": "./models", - "@error": "./error", - "@root": "./", - "@chinlinlee": "./models/DICOM/dcm4che/wrapper/org/github/chinlinlee", - "@dbModels": "./models/sql/models", - "@dbInitializer": "./models/sql/initializer.js", - "@dicom-json-model": "./models/sql/dicom-json-model.js", - "@query-json-factory": "./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js" - }, "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", "archiver": "^5.3.1", diff --git a/server.js b/server.js index 82716e37..df5511c4 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,12 @@ RegExp.prototype.toJSON = RegExp.prototype.toString; -require('module-alias')(__dirname); +const { raccoonConfig } = require("./config-class"); + +if (raccoonConfig.serverConfig.dbType === "mongodb") { + require('module-alias')(__dirname + "/config/modula-alias/mongodb"); +} else if (raccoonConfig.serverConfig.dbType === "sql") { + require('module-alias')(__dirname + "/config/modula-alias/sql"); +} + const { app, server } = require("./app"); const bodyParser = require("body-parser"); @@ -12,7 +19,6 @@ const SequelizeStore = require("connect-session-sequelize")(session.Store); const sequelizeInstance = require("./models/sql/instance"); const passport = require("passport"); -const { raccoonConfig } = require("./config-class"); const { DcmQrScp } = require('./dimse-sql'); require("dotenv"); require("./websocket"); From 90def2d95dd312c1627135e01d570f5eaf62de98 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 19:46:36 +0800 Subject: [PATCH 156/365] refactor: use module-alias to modify sql version QIDO-RS - Remove allPatient.js, queryAllInstances.js, queryAllSeries.js, queryAllStudies.js, queryStudies-Instances.js, queryStudies-Series-Instance.js, queryStudies-Series.js. - This commit removes the unnecessary code for querying patients, instances, series, and studies from the application. --- .../controller/QIDO-RS/allPatient.js | 46 ------ .../controller/QIDO-RS/queryAllInstances.js | 46 ------ .../controller/QIDO-RS/queryAllSeries.js | 47 ------ .../controller/QIDO-RS/queryAllStudies.js | 49 ------ .../QIDO-RS/queryStudies-Instances.js | 47 ------ .../QIDO-RS/queryStudies-Series-Instance.js | 49 ------ .../controller/QIDO-RS/queryStudies-Series.js | 47 ------ .../QIDO-RS/service/QIDO-RS.service.js | 149 ++---------------- .../service/query-dicom-json-factory.js | 17 ++ .../controller/QIDO-RS/base.controller.js | 2 +- .../QIDO-RS/service/QIDO-RS.service.js | 14 +- .../service/query-dicom-json-factory.js | 1 + dimse-sql/queryBuilder.js | 3 +- routes.js | 2 +- 14 files changed, 42 insertions(+), 477 deletions(-) delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/allPatient.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js create mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/allPatient.js b/api-sql/dicom-web/controller/QIDO-RS/allPatient.js deleted file mode 100644 index b2034f3e..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/allPatient.js +++ /dev/null @@ -1,46 +0,0 @@ -const { - SqlQidoRsService: QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class QueryAllPatientsController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info("Query all patients"); - - try { - let qidoRsService = new QidoRsService(this.request, this.response, "patient"); - - await qidoRsService.getAndResponseDicomJson(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllPatientsController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js deleted file mode 100644 index cedf9169..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/queryAllInstances.js +++ /dev/null @@ -1,46 +0,0 @@ -const { - SqlQidoRsService: QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class QueryAllInstancesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info("Query all instances"); - - try { - let qidoRsService = new QidoRsService(this.request, this.response, "instance"); - - await qidoRsService.getAndResponseDicomJson(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllInstancesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js deleted file mode 100644 index dcb9b3c9..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/queryAllSeries.js +++ /dev/null @@ -1,47 +0,0 @@ -const { - SqlQidoRsService: QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class QueryAllSeriesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info("Query all series"); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "series"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllSeriesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js b/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js deleted file mode 100644 index ca2096ab..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js +++ /dev/null @@ -1,49 +0,0 @@ -const { - SqlQidoRsService: QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class QueryAllStudiesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query All Studies`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "study"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllStudiesController(req, res); - - await controller.doPipeline(); -}; - diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js deleted file mode 100644 index 682913a4..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Instances.js +++ /dev/null @@ -1,47 +0,0 @@ -const { - SqlQidoRsService: QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class QueryInstancesOfStudiesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query instances in study, Study UID: ${this.request.params.studyUID}`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "instance"); - - await qidoRsService.getAndResponseDicomJson(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryInstancesOfStudiesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js deleted file mode 100644 index 005a986d..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js +++ /dev/null @@ -1,49 +0,0 @@ -const _ = require("lodash"); -const { - SqlQidoRsService: QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class QueryInstancesOfSeriesOfStudiesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query instance Level, Study UID: ${this.request.params.studyUID}, Series UID: ${this.request.params.seriesUID}`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "instance"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryInstancesOfSeriesOfStudiesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js b/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js deleted file mode 100644 index 0d6e8215..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/queryStudies-Series.js +++ /dev/null @@ -1,47 +0,0 @@ -const { - SqlQidoRsService: QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class QuerySeriesOfStudiesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "QIDO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`Query series Level, Study UID: ${this.request.params.studyUID}`); - - try { - - let qidoRsService = new QidoRsService(this.request, this.response, "series"); - - await qidoRsService.getAndResponseDicomJson(); - - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } -} -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QuerySeriesOfStudiesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 0dfb8dfb..7f594750 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -1,143 +1,16 @@ const _ = require("lodash"); -const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); -const { DicomWebService } = require("@root/api/dicom-web/service/dicom-web.service"); -const { StudyQueryBuilder } = require("./querybuilder"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { - DicomWebServiceError, - DicomWebStatusCodes -} = require("@error/dicom-web-service"); -const { StudyModel } = require("@models/sql/models/study.model"); -const { SeriesModel } = require("@models/sql/models/series.model"); -const { InstanceModel } = require("@models/sql/models/instance.model"); -const { PatientModel } = require("@models/sql/models/patient.model"); - - -class SqlQidoRsService extends QidoRsService { - /** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {"study" | "series" | "instance"} level - */ - constructor(req, res, level = "instance") { - super(req, res, level); +const { QidoRsService, convertAllQueryToDICOMTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); + +QidoRsService.prototype.initQuery_ = function () { + let query = _.cloneDeep(this.request.query); + let queryKeys = Object.keys(query).sort(); + for (let i = 0; i < queryKeys.length; i++) { + let queryKey = queryKeys[i]; + if (!query[queryKey]) delete query[queryKey]; } - async getAndResponseDicomJson() { - try { - - let dicomWebService = new DicomWebService(this.request, this.response); - - let queryOptions = { - query: this.query, - skip: this.skip_, - limit: this.limit_, - includeFields: this.includeFields_, - retrieveBaseUrl: `${dicomWebService.getBasicURL()}/studies`, - requestParams: this.request.params - }; - - let qidoDicomJsonFactory = new QidoDicomJsonFactory(queryOptions, this.level); - - let dicomJson = await qidoDicomJsonFactory.getDicomJson(); - - let dicomJsonLength = _.get(dicomJson, "length", 0); - if (dicomJsonLength > 0) { - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(dicomJson)); - } else { - this.response.writeHead(204); - this.response.end(); - } - - } catch (e) { - throw e; - } - } - - /** - * @private - */ - initQuery_() { - let query = _.cloneDeep(this.request.query); - let queryKeys = Object.keys(query).sort(); - for (let i = 0; i < queryKeys.length; i++) { - let queryKey = queryKeys[i]; - if (!query[queryKey]) delete query[queryKey]; - } - - this.query = convertAllQueryToDicomTag(query); - } -} - -class QidoDicomJsonFactory { - - /** - * - * @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions - * @param {string} level - */ - constructor(queryOptions, level = "instance") { - this.level = level; - - this.getDicomJsonByLevel = { - "patient": async () => { - return await PatientModel.getDicomJson(queryOptions); - }, - "study": async () => { - return await StudyModel.getDicomJson(queryOptions); - }, - "series": async () => { - return await SeriesModel.getDicomJson(queryOptions); - }, - "instance": async () => { - return await InstanceModel.getDicomJson(queryOptions); - } - }; - } - - async getDicomJson() { - return await this.getDicomJsonByLevel[this.level](); - } -} - - -/** - * Convert All of name(tags, keyword) of queries to tags number - * @param {Object} iParam The request query. - * @returns - */ -function convertAllQueryToDicomTag(iParam) { - let keys = Object.keys(iParam); - let newQS = {}; - for (let i = 0; i < keys.length; i++) { - let keyName = keys[i]; - let keyNameSplit = keyName.split("."); - let newKeyNames = []; - for (let x = 0; x < keyNameSplit.length; x++) { - if (dictionary.keyword[keyNameSplit[x]]) { - newKeyNames.push(dictionary.keyword[keyNameSplit[x]]); - } else if (dictionary.tag[keyNameSplit[x]]) { - newKeyNames.push(keyNameSplit[x]); - } - } - if (newKeyNames.length === 0) { - throw new DicomWebServiceError( - DicomWebStatusCodes.InvalidArgumentValue, - `Invalid request query: ${keyNameSplit}`, - 400 - ); - } - - let retKeyName = newKeyNames.join("."); - newQS[retKeyName] = iParam[keyName]; - } - return newQS; -} + this.query = convertAllQueryToDICOMTag(query, false); +}; -module.exports.SqlQidoRsService = SqlQidoRsService; -module.exports.convertAllQueryToDicomTag = convertAllQueryToDicomTag; \ No newline at end of file +module.exports.QidoRsService = QidoRsService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js new file mode 100644 index 00000000..7921b927 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -0,0 +1,17 @@ +const { + QueryDicomJsonFactory, + QueryPatientDicomJsonFactory, + QueryStudyDicomJsonFactory, + QuerySeriesDicomJsonFactory, + QueryInstanceDicomJsonFactory +} = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); + +QueryDicomJsonFactory.prototype.getDicomJson = async function () { + return await this.model.getDicomJson(this.queryOptions); +}; + +module.exports.QueryDicomJsonFactory = QueryDicomJsonFactory; +module.exports.QueryPatientDicomJsonFactory = QueryPatientDicomJsonFactory; +module.exports.QueryStudyDicomJsonFactory = QueryStudyDicomJsonFactory; +module.exports.QuerySeriesDicomJsonFactory = QuerySeriesDicomJsonFactory; +module.exports.QueryInstanceDicomJsonFactory = QueryInstanceDicomJsonFactory; \ No newline at end of file diff --git a/api/dicom-web/controller/QIDO-RS/base.controller.js b/api/dicom-web/controller/QIDO-RS/base.controller.js index d94d85e1..ce69d448 100644 --- a/api/dicom-web/controller/QIDO-RS/base.controller.js +++ b/api/dicom-web/controller/QIDO-RS/base.controller.js @@ -1,6 +1,6 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { QidoRsService } = require("./service/QIDO-RS.service"); +const { QidoRsService } = require("@qido-rs-service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseQueryController extends Controller { diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 38f02d49..56d4acda 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -139,7 +139,7 @@ class QidoRsService { * @param {Object} iParam The request query. * @returns */ -function convertAllQueryToDICOMTag(iParam) { +function convertAllQueryToDICOMTag(iParam, pushSuffixValue=true) { let keys = Object.keys(iParam); let newQS = {}; for (let i = 0; i < keys.length; i++) { @@ -160,10 +160,16 @@ function convertAllQueryToDICOMTag(iParam) { `Invalid request query: ${keyNameSplit}`, 400 ); - } else if (newKeyNames.length >= 2) { - retKeyName = newKeyNames.map(v => v + ".Value").join("."); + } + + if (pushSuffixValue) { + if (newKeyNames.length >= 2) { + retKeyName = newKeyNames.map(v => v + ".Value").join("."); + } else { + newKeyNames.push("Value"); + retKeyName = newKeyNames.join("."); + } } else { - newKeyNames.push("Value"); retKeyName = newKeyNames.join("."); } diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index b123280c..3a020c86 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -204,6 +204,7 @@ class QueryInstanceDicomJsonFactory extends QueryDicomJsonFactory { } } +module.exports.QueryDicomJsonFactory = QueryDicomJsonFactory; module.exports.QueryPatientDicomJsonFactory = QueryPatientDicomJsonFactory; module.exports.QueryStudyDicomJsonFactory = QueryStudyDicomJsonFactory; module.exports.QuerySeriesDicomJsonFactory = QuerySeriesDicomJsonFactory; diff --git a/dimse-sql/queryBuilder.js b/dimse-sql/queryBuilder.js index 806f7178..65a4160e 100644 --- a/dimse-sql/queryBuilder.js +++ b/dimse-sql/queryBuilder.js @@ -1,7 +1,6 @@ const _ = require("lodash"); -const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); -const { convertAllQueryToDicomTag } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { DimseQueryBuilder } = require("@root/dimse/queryBuilder"); diff --git a/routes.js b/routes.js index 437d2d00..ce52abc6 100644 --- a/routes.js +++ b/routes.js @@ -20,7 +20,7 @@ module.exports = function (app) { loadAllPlugin(); app.use("/dicom-web", require("./api/dicom-web/stow-rs.route")); - app.use("/dicom-web", require("./api-sql/dicom-web/qido-rs.route")); + app.use("/dicom-web", require("./api/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); From 6b667a29a86018ff519e1191ce5bc87b88914af5 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 19:48:15 +0800 Subject: [PATCH 157/365] fix: `getAuditInstancesInfoFromStudyUID` not handle empty studyUID - Handle empty studyUID in getAuditInstancesInfoFromStudyUID function to prevent unnecessary database query. --- models/sql/models/instance.model.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index 3783a8a8..251bf99f 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -37,18 +37,19 @@ class InstanceModel extends Model { * @param {string} studyUID */ static async getAuditInstancesInfoFromStudyUID(studyUID) { - let instances = await sequelizeInstance.model("Instance").findAll({ - where: { - x0020000D: studyUID - } - }); - let instanceInfos = { sopClassUIDs: [], accessionNumbers: [], patientID: "", patientName: "" }; + if (!studyUID) return instanceInfos; + + let instances = await sequelizeInstance.model("Instance").findAll({ + where: { + x0020000D: studyUID + } + }); for (let instance of instances) { let sopClassUID = instance.x00080016; From c9ca0ff6840732f19d21664c4c3dbd4978c29673 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 20:32:33 +0800 Subject: [PATCH 158/365] fix: incorrect args position in `raiseInternalServerError` --- error/api-errors.handler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error/api-errors.handler.js b/error/api-errors.handler.js index eba07b06..bcb2c0b6 100644 --- a/error/api-errors.handler.js +++ b/error/api-errors.handler.js @@ -86,7 +86,7 @@ class ApiErrorArrayHandler { * @param {ApiLogger} apiLogger * @param {Error} e */ - static raiseInternalServerError(response, apiLogger, e) { + static raiseInternalServerError(e, response, apiLogger) { apiLogger.logger.error(e); if (!response.headersSent) { From 34a172590b35a9700570d38fcc814695f5030cea Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 20:35:21 +0800 Subject: [PATCH 159/365] refactor: sql WADO-RS service - Override StudyImagePathFactory, SeriesImagePathFactory, and InstanceImagePathFactory classes to handle image path retrieval. - Updated package.json to include aliases for the WADO-RS and bulkdata --- .../WADO-RS/service/WADO-RS.service.js | 132 +++--------------- config/jsconfig.sql.json | 2 + config/modula-alias/sql/package.json | 4 +- 3 files changed, 26 insertions(+), 112 deletions(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 73776f0e..4efe492f 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -1,128 +1,37 @@ -const _ = require("lodash"); -const fsP = require("fs/promises"); const { InstanceModel } = require("@models/sql/models/instance.model"); const { StudyModel } = require("@models/sql/models/study.model"); const { SeriesModel } = require("@models/sql/models/series.model"); -const { +const { getAcceptType, - supportInstanceMultipartType, - sendNotSupportedMediaType, - addHostnameOfBulkDataUrl, - multipartContentTypeWriter, - ImageMultipartWriter + supportInstanceMultipartType, + sendNotSupportedMediaType, + addHostnameOfBulkDataUrl, + multipartContentTypeWriter, + ImageMultipartWriter, + getUidsString, + ImagePathFactory, + StudyImagePathFactory, + SeriesImagePathFactory, + InstanceImagePathFactory } = require("@root/api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); -class ImagePathFactory { +StudyImagePathFactory.prototype.getImagePaths = async function () { + this.imagePaths = await StudyModel.getPathGroupOfInstances(this.uids); +}; - /** - * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids - */ - constructor(uids) { - /** @type { import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[] } */ - this.imagePaths = []; - /** @type {Uids} */ - this.uids = uids; - } +SeriesImagePathFactory.prototype.getImagePaths = async function () { + this.imagePaths = await SeriesModel.getPathGroupOfInstances(this.uids); +}; - async getImagePaths() { } - - async checkAllImageExist() { - if (this.imagePaths.length === 0) { - return { - status: false, - code: 404, - message: `not found, ${this.getUidsString()}` - }; - } - - let existArr = await this.getImageExistArray(); - if (existArr.every(v => v)) { - return { - status: true, - code: 200 - }; - } else if (existArr.some(v => v)) { - return { - status: true, - code: 206 - }; - } else { - return { - status: false, - code: 410, - message: "Images gone, but data exist" - }; - } - } - - async getImageExistArray() { - /** @type {boolean[]} */ - let existArr = []; - let imagePathsClone = _.cloneDeep(this.imagePaths); - for (let i = 0; i < imagePathsClone.length; i++) { - let imagePathObj = imagePathsClone[i]; - try { - await fsP.access(imagePathObj.instancePath, fsP.constants.F_OK); - existArr.push(true); - } catch (e) { - this.imagePaths.splice(i, 1); - existArr.push(false); - } - } - return existArr; - } - - getUidsString() { - let uidsKeys = Object.keys(this.uids); - let strArr = []; - for (let i = 0; i < uidsKeys.length; i++) { - let key = uidsKeys[i]; - strArr.push(`${key}: ${this.uids[key]}`); - } - return strArr.join(", "); - } - - getPartialImagesPathString() { - return JSON.stringify(this.imagePaths.slice(0, 10).map(v => v.instancePath)); - } -} - -class StudyImagePathFactory extends ImagePathFactory { - constructor(uids) { - super(uids); - } - - async getImagePaths() { - this.imagePaths = await StudyModel.getPathGroupOfInstances(this.uids); - } -} - -class SeriesImagePathFactory extends ImagePathFactory { - constructor(uids) { - super(uids); - } - - async getImagePaths() { - this.imagePaths = await SeriesModel.getPathGroupOfInstances(this.uids); - } -} - -class InstanceImagePathFactory extends ImagePathFactory { - constructor(uids) { - super(uids); - } - - async getImagePaths() { - let imagePath = await InstanceModel.getPathOfInstance(this.uids); +InstanceImagePathFactory.prototype.getImagePaths = async function () { + let imagePath = await InstanceModel.getPathOfInstance(this.uids); if (imagePath) this.imagePaths = [imagePath]; else this.imagePaths = []; - } -} +}; module.exports.getAcceptType = getAcceptType; module.exports.supportInstanceMultipartType = supportInstanceMultipartType; @@ -134,3 +43,4 @@ module.exports.SeriesImagePathFactory = SeriesImagePathFactory; module.exports.InstanceImagePathFactory = InstanceImagePathFactory; module.exports.multipartContentTypeWriter = multipartContentTypeWriter; module.exports.ImageMultipartWriter = ImageMultipartWriter; +module.exports.getUidsString = getUidsString; \ No newline at end of file diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index 19a55116..d910ba64 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -12,6 +12,8 @@ "@dicom-json-model": ["./models/sql/dicom-json-model.js"], "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"], "@qido-rs-service": ["./api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], + "@wado-rs-service": ["./api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], + "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"] } }, "exclude": [ diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index 76e89807..60df36b3 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -10,6 +10,8 @@ "@dbInitializer": "../../../models/sql/initializer.js", "@dicom-json-model": "../../../models/sql/dicom-json-model.js", "@query-dicom-json-factory": "../../../api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js", - "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js" + "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", + "@wado-rs-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", + "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js" } } \ No newline at end of file From fec4848cc0853b03eb418ef7b9a98a739da19a3a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 20:41:03 +0800 Subject: [PATCH 160/365] refactor: use module-alias refactoring bulkdata service - Removed original sql version of bulkdata.js, instance.js, series.js, study.js. - Refactored bulkdata.js service file. - Changed import and export for BulkDataService, StudyBulkDataFactory, SeriesBulkDataFactory, InstanceBulkDataFactory, and SpecificBulkDataFactory --- .../controller/WADO-RS/bulkdata/bulkdata.js | 48 ------ .../controller/WADO-RS/bulkdata/instance.js | 59 -------- .../controller/WADO-RS/bulkdata/series.js | 59 -------- .../WADO-RS/bulkdata/service/bulkdata.js | 140 +++++++++--------- .../controller/WADO-RS/bulkdata/study.js | 57 ------- api-sql/dicom-web/wado-rs-bulkdata.route.js | 88 ----------- .../WADO-RS/bulkdata/base.controller.js | 5 +- .../controller/WADO-RS/bulkdata/bulkdata.js | 8 +- .../controller/WADO-RS/bulkdata/instance.js | 7 +- .../controller/WADO-RS/bulkdata/series.js | 4 +- .../controller/WADO-RS/bulkdata/study.js | 4 +- routes.js | 2 +- 12 files changed, 78 insertions(+), 403 deletions(-) delete mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js delete mode 100644 api-sql/dicom-web/wado-rs-bulkdata.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js deleted file mode 100644 index bba09900..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ /dev/null @@ -1,48 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); -const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); - -class BulkDataController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data ${this.request.params.binaryValuePath}\ -, from StudyInstanceUID: ${this.request.params.studyUID}\ -, SeriesInstanceUID: ${this.request.params.seriesUID}\ -, SOPInstanceUID: ${this.request.params.instanceUID}`); - - let bulkDataService = new BulkDataService(this.request, this.response); - - try { - let bulkData = await bulkDataService.getSpecificBulkData(); - await bulkDataService.writeBulkData(bulkData); - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch(e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let bulkDataController = new BulkDataController(req, res); - - await bulkDataController.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js deleted file mode 100644 index e54bc21f..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ /dev/null @@ -1,59 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); -const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); -const { InstanceModel } = require("@models/sql/models/instance.model"); - -class InstanceBulkDataController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ -, SeriesInstanceUID: ${this.request.params.seriesUID}\ -, SOPInstanceUID: ${this.request.params.instanceUID}`); - - let bulkDataService = new BulkDataService(this.request, this.response); - - try { - let bulkDataArray = await bulkDataService.getInstanceBulkData(); - for (let bulkData of bulkDataArray) { - await bulkDataService.writeBulkData(bulkData); - } - - let dicomInstancePathObj = await InstanceModel.getPathOfInstance({ - studyUID: this.request.params.studyUID, - seriesUID: this.request.params.seriesUID, - instanceUID: this.request.params.instanceUID - }); - - await bulkDataService.writeBulkData(dicomInstancePathObj); - - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch(e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let instanceBulkDataController = new InstanceBulkDataController(req, res); - - await instanceBulkDataController.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js deleted file mode 100644 index 538b80d2..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/bulkdata/series.js +++ /dev/null @@ -1,59 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); -const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); -const { SeriesModel } = require("@models/sql/models/series.model"); - -class SeriesBulkDataController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ -, SeriesInstanceUID: ${this.request.params.seriesUID}`); - - let bulkDataService = new BulkDataService(this.request, this.response); - - try { - let bulkDataArray = await bulkDataService.getSeriesBulkData(); - for (let bulkData of bulkDataArray) { - await bulkDataService.writeBulkData(bulkData); - } - - let dicomInstancePathObjArray = await SeriesModel.getPathGroupOfInstances({ - studyUID: this.request.params.studyUID, - seriesUID: this.request.params.seriesUID - }); - - for (let instancePathObj of dicomInstancePathObjArray) { - await bulkDataService.writeBulkData(instancePathObj); - } - - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch (e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function (req, res) { - let seriesBulkDataController = new SeriesBulkDataController(req, res); - - await seriesBulkDataController.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index 9fbd16b6..22a6ec05 100644 --- a/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -1,92 +1,88 @@ -const { BulkDataService } = require("@root/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); +const { + BulkDataService, + StudyBulkDataFactory, + SeriesBulkDataFactory, + InstanceBulkDataFactory, + SpecificBulkDataFactory +} = require("@root/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); const { DicomBulkDataModel } = require("@models/sql/models/dicomBulkData.model"); const { Op } = require("sequelize"); -class SqlBulkDataService extends BulkDataService { - /** - * - * @param {import("express").Request} req - * @param {import("express").Response} res - */ - constructor(req, res) { - super(req, res); - } +StudyBulkDataFactory.prototype.getBulkData = async function () { + let { + studyUID + } = this.uids; - - async getSpecificBulkData() { - - let { - studyUID, - seriesUID, - instanceUID, - binaryValuePath - } = this.request.params; - - /** @type { import("sequelize").FindOptions } */ - let findOption = { - where: { - studyUID, - seriesUID, - instanceUID, - binaryValuePath: { - [Op.like]: `%${binaryValuePath}%` - } - } - }; - - let bulkData = await DicomBulkDataModel.findOne(findOption); - - return bulkData; - } - - async getStudyBulkData() { - let { + let studyBulkDataArray = await DicomBulkDataModel.findAll({ + where: { studyUID - } = this.request.params; + } + }); - let studyBulkDataArray = await DicomBulkDataModel.findAll({ - where: { - studyUID - } - }); + return studyBulkDataArray; +}; - return studyBulkDataArray; - } +SeriesBulkDataFactory.prototype.getBulkData = async function () { + let { + studyUID, + seriesUID + } = this.uids; - async getSeriesBulkData() { - let { + let seriesBulkDataArray = await DicomBulkDataModel.findAll({ + where: { studyUID, seriesUID - } = this.request.params; + } + }); - let seriesBulkDataArray = await DicomBulkDataModel.findAll({ - where: { - studyUID, - seriesUID - } - }); + return seriesBulkDataArray; +}; - return seriesBulkDataArray; - } +InstanceBulkDataFactory.prototype.getBulkData = async function () { + let { + studyUID, + seriesUID, + instanceUID + } = this.uids; - async getInstanceBulkData() { - let { + let instanceBulkDataArray = await DicomBulkDataModel.findAll({ + where: { studyUID, seriesUID, instanceUID - } = this.request.params; - - let instanceBulkDataArray = await DicomBulkDataModel.findAll({ - where: { - studyUID, - seriesUID, - instanceUID + } + }); + + return instanceBulkDataArray; +}; + +SpecificBulkDataFactory.prototype.getBulkData = async function () { + let { + studyUID, + seriesUID, + instanceUID, + binaryValuePath + } = this.uids; + + /** @type { import("sequelize").FindOptions } */ + let findOption = { + where: { + studyUID, + seriesUID, + instanceUID, + binaryValuePath: { + [Op.like]: `%${binaryValuePath}%` } - }); + } + }; - return instanceBulkDataArray; - } -} + let bulkData = await DicomBulkDataModel.findOne(findOption); + return bulkData; +}; -module.exports.BulkDataService = SqlBulkDataService; \ No newline at end of file +module.exports.BulkDataService = BulkDataService; +module.exports.StudyBulkDataFactory = StudyBulkDataFactory; +module.exports.SeriesBulkDataFactory = SeriesBulkDataFactory; +module.exports.InstanceBulkDataFactory = InstanceBulkDataFactory; +module.exports.SpecificBulkDataFactory = SpecificBulkDataFactory; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js deleted file mode 100644 index abaf2d16..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/bulkdata/study.js +++ /dev/null @@ -1,57 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { BulkDataService } = require("./service/bulkdata"); -const { getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); -const { StudyModel } = require("@models/sql/models/study.model"); - -class StudyBulkDataController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}`); - - let bulkDataService = new BulkDataService(this.request, this.response); - - try { - let bulkDataArray = await bulkDataService.getStudyBulkData(); - for (let bulkData of bulkDataArray) { - await bulkDataService.writeBulkData(bulkData); - } - - let dicomInstancePathObjArray = await StudyModel.getPathGroupOfInstances({ - studyUID: this.request.params.studyUID - }); - - for(let instancePathObj of dicomInstancePathObjArray) { - await bulkDataService.writeBulkData(instancePathObj); - } - - bulkDataService.multipartWriter.writeFinalBoundary(); - return this.response.end(); - } catch(e) { - apiLogger.logger.error(e); - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let studyBulkDataController = new StudyBulkDataController(req, res); - - await studyBulkDataController.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/wado-rs-bulkdata.route.js b/api-sql/dicom-web/wado-rs-bulkdata.route.js deleted file mode 100644 index e3617a5f..00000000 --- a/api-sql/dicom-web/wado-rs-bulkdata.route.js +++ /dev/null @@ -1,88 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi } = require("@root/api/validator"); -const router = express(); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/bulkdata: - * get: - * tags: - * - WADO-RS - * description: Retrieve study's bulk data - * parameters: - * - $ref: "#/components/parameters/studyUID" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedOctet" - * - */ -router.get( - "/studies/:studyUID/bulkdata", - require("./controller/WADO-RS/bulkdata/study") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/bulkdata: - * get: - * tags: - * - WADO-RS - * description: Retrieve series's bulk data - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedOctet" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/bulkdata", - require("./controller/WADO-RS/bulkdata/series") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/bulkdata: - * get: - * tags: - * - WADO-RS - * description: Retrieve instance's bulk data - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedOctet" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/bulkdata", - require("./controller/WADO-RS/bulkdata/instance") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/bulkdata/{binaryValuePath}: - * get: - * tags: - * - WADO-RS - * description: Retrieve instance's bulk data of specific tag - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * - $ref: "#/components/parameters/binaryValuePath" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedOctet" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/bulkdata/:binaryValuePath", - require("./controller/WADO-RS/bulkdata/bulkdata") -); - -module.exports = router; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js index cfb4dbdc..3d1e56d1 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js @@ -1,7 +1,7 @@ const { Controller } = require("@root/api/controller.class"); -const { StudyBulkDataFactory, BulkDataService } = require("./service/bulkdata"); +const { StudyBulkDataFactory, BulkDataService } = require("@bulkdata-service"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { StudyImagePathFactory } = require("@wado-rs-service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseBulkDataController extends Controller { @@ -22,7 +22,6 @@ class BaseBulkDataController extends Controller { this.logAction(); try { - this.logAction(); let bulkData = await this.bulkDataService.getBulkData(); if (Array.isArray(bulkData)) { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js index 419c5297..7da20b55 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js @@ -1,11 +1,5 @@ -const mongoose = require("mongoose"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { BulkDataService, SpecificBulkDataFactory } = require("./service/bulkdata"); -const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); +const { SpecificBulkDataFactory } = require("@bulkdata-service"); const { BaseBulkDataController } = require("./base.controller"); -const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js index b164cd33..eaa5e16a 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js @@ -1,9 +1,6 @@ -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { BulkDataService, InstanceBulkDataFactory } = require("./service/bulkdata"); -const { getInternalServerErrorMessage } = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { InstanceModel } = require("@dbModels/instance.model"); +const { InstanceBulkDataFactory } = require("@bulkdata-service"); const { BaseBulkDataController } = require("./base.controller"); -const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); +const { InstanceImagePathFactory } = require("@wado-rs-service"); class InstanceBulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/series.js b/api/dicom-web/controller/WADO-RS/bulkdata/series.js index ea2d816b..e66164be 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/series.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/series.js @@ -1,6 +1,6 @@ -const { SeriesBulkDataFactory } = require("./service/bulkdata"); +const { SeriesBulkDataFactory } = require("@bulkdata-service"); const { BaseBulkDataController } = require("./base.controller"); -const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); +const { SeriesImagePathFactory } = require("@wado-rs-service"); class SeriesBulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/study.js b/api/dicom-web/controller/WADO-RS/bulkdata/study.js index 9b499e59..01f00204 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/study.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/study.js @@ -1,6 +1,6 @@ -const { StudyBulkDataFactory } = require("./service/bulkdata"); +const { StudyBulkDataFactory } = require("@bulkdata-service"); const { BaseBulkDataController } = require("./base.controller"); -const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { StudyImagePathFactory } = require("@wado-rs-service"); class StudyBulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/routes.js b/routes.js index ce52abc6..7aa15c6f 100644 --- a/routes.js +++ b/routes.js @@ -24,7 +24,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); - app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-bulkdata.route")); + app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api-sql/dicom-web/delete.route")); // app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); From 8ed47eb6769cb752767fe47ada910278f381e230 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 20:53:01 +0800 Subject: [PATCH 161/365] refactor: use module-alias for WADO-RS retrieve instances --- .../controller/WADO-RS/retrieveInstance.js | 84 ------------- .../WADO-RS/retrieveStudy-Series-Instances.js | 80 ------------- .../WADO-RS/retrieveStudyInstances.js | 80 ------------- .../controller/WADO-RS/service/WADOZip.js | 110 ------------------ api-sql/dicom-web/wado-rs-instance.route.js | 70 ----------- .../controller/WADO-RS/base.controller.js | 12 +- .../controller/WADO-RS/service/WADOZip.js | 8 +- routes.js | 2 +- 8 files changed, 15 insertions(+), 431 deletions(-) delete mode 100644 api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js delete mode 100644 api-sql/dicom-web/wado-rs-instance.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js b/api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js deleted file mode 100644 index e6fb18cd..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/retrieveInstance.js +++ /dev/null @@ -1,84 +0,0 @@ -const wadoService = require("./service/WADO-RS.service"); -const { WADOZip } = require("./service/WADOZip"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); -class RetrieveInstanceOfSeriesOfStudiesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); - apiLogger.logger.info(`Request Accept: ${this.request.headers.accept}`); - - try { - - if (this.request.headers.accept.toLowerCase() === "application/zip") { - return await this.responseZip(); - } else if (this.request.headers.accept.includes("multipart/related")) { - return await this.responseMultipartRelated(); - } else if (this.request.headers.accept.includes("*")){ - this.request.headers.accept = "multipart/related; type=\"application/dicom\""; - return await this.responseMultipartRelated(); - } - - return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } - - async responseZip() { - let wadoZip = new WADOZip(this.request.params, this.response); - let zipResult = await wadoZip.getZipOfInstanceDICOMFile(); - if (zipResult.status) { - return this.response.end(); - } else { - this.response.writeHead(zipResult.code, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(zipResult)); - } - } - - async responseMultipartRelated() { - let type = wadoService.getAcceptType(this.request); - let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; - if (!isSupported) { - return wadoService.sendNotSupportedMediaType(this.response, type); - } - - let imageMultipartWriter = new wadoService.ImageMultipartWriter( - this.request, - this.response, - wadoService.InstanceImagePathFactory, - wadoService.multipartContentTypeWriter[type] - ); - - return await imageMultipartWriter.write(); - } -} - - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveInstanceOfSeriesOfStudiesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js b/api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js deleted file mode 100644 index bd5a2d6d..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js +++ /dev/null @@ -1,80 +0,0 @@ -const { logger } = require("@root/utils/logs/log"); -const wadoService = require("./service/WADO-RS.service"); -const { WADOZip } = require("./service/WADOZip"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { Controller } = require("@root/api/controller.class"); - -class RetrieveInstancesOfSeries extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - try { - logger.info(`[WADO-RS] [Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}] [Request Accept: ${this.request.headers.accept}]`); - - if (this.request.headers.accept.toLowerCase() === "application/zip") { - return await this.responseZip(); - } else if (this.request.headers.accept.includes("multipart/related")) { - return await this.responseMultipartRelated(); - } else if (this.request.headers.accept.includes("*")){ - this.request.headers.accept = "multipart/related; type=\"application/dicom\""; - return await this.responseMultipartRelated(); - } - - return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - logger.error(`[WADO-RS] [Error: ${errorStr}]`); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } - - async responseZip() { - let wadoZip = new WADOZip(this.request.params, this.response); - let zipResult = await wadoZip.getZipOfSeriesDICOMFiles(); - if (zipResult.status) { - return this.response.end(); - } else { - this.response.writeHead(zipResult.code, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(zipResult)); - } - } - - async responseMultipartRelated() { - let type = wadoService.getAcceptType(this.request); - let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; - if (!isSupported) { - return wadoService.sendNotSupportedMediaType(this.response, type); - } - - let imageMultipartWriter = new wadoService.ImageMultipartWriter( - this.request, - this.response, - wadoService.SeriesImagePathFactory, - wadoService.multipartContentTypeWriter[type] - ); - - return await imageMultipartWriter.write(); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveInstancesOfSeries(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js b/api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js deleted file mode 100644 index 3ad67fef..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/retrieveStudyInstances.js +++ /dev/null @@ -1,80 +0,0 @@ -const { logger } = require("@root/utils/logs/log"); -const wadoService = require("./service/WADO-RS.service"); -const { WADOZip } = require("./service/WADOZip"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { Controller } = require("@root/api/controller.class"); - -class RetrieveStudyInstancesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - try { - logger.info(`[WADO-RS] [Get study's instances, study UID: ${this.request.params.studyUID}] [Request Accept: ${this.request.headers.accept}]`); - - if (this.request.headers.accept.toLowerCase() === "application/zip") { - return await this.responseZip(); - } else if (this.request.headers.accept.includes("multipart/related")) { - return await this.responseMultipartRelated(); - } else if (this.request.headers.accept.includes("*")) { - this.request.headers.accept = "multipart/related; type=\"application/dicom\""; - return await this.responseMultipartRelated(); - } - - return wadoService.sendNotSupportedMediaType(this.response, this.request.headers.accept); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - logger.error(`[WADO-RS] [Error: ${errorStr}]`); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: "Server error occurred" - })); - } - } - - async responseZip() { - let wadoZip = new WADOZip(this.request.params, this.response); - let zipResult = await wadoZip.getZipOfStudyDICOMFiles(); - if (zipResult.status) { - return this.response.end(); - } else { - this.response.writeHead(zipResult.code, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(zipResult)); - } - } - - async responseMultipartRelated() { - let type = wadoService.getAcceptType(this.request); - let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1; - if (!isSupported) { - return wadoService.sendNotSupportedMediaType(this.response, type); - } - - let imageMultipartWriter = new wadoService.ImageMultipartWriter( - this.request, - this.response, - wadoService.StudyImagePathFactory, - wadoService.multipartContentTypeWriter[type] - ); - - return await imageMultipartWriter.write(); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveStudyInstancesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js b/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js deleted file mode 100644 index 1fd58baf..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/service/WADOZip.js +++ /dev/null @@ -1,110 +0,0 @@ -const archiver = require("archiver"); -const wadoService = require("./WADO-RS.service"); -const path = require("path"); -const { StudyModel } = require("@models/sql/models/study.model"); -const { SeriesModel } = require("@models/sql/models/series.model"); -const { InstanceModel } = require("@models/sql/models/instance.model"); - -class WADOZip { - constructor(iParam, iRes) { - this.requestParams = iParam; - this.studyUID = iParam.studyUID; - this.seriesUID = iParam.seriesUID; - this.instanceUID = iParam.instanceUID; - this.res = iRes; - } - - setHeaders(uid) { - this.res.attachment = `${uid}.zip`; - this.res.setHeader('Content-Type', 'application/zip'); - this.res.setHeader('Content-Disposition', `attachment; filename=${uid}.zip`); - } - - async getZipOfStudyDICOMFiles() { - let imagesPathList = await StudyModel.getPathGroupOfInstances(this.requestParams); - if (imagesPathList.length > 0) { - this.setHeaders(this.studyUID); - - let folders = []; - for (let i = 0; i < imagesPathList.length; i++) { - let imagesFolder = path.dirname(imagesPathList[i].instancePath); - if (!folders.includes(imagesFolder)) { - folders.push(imagesFolder); - } - } - return await toZip(this.res, folders); - } - return { - status: false, - code: 404, - message: `not found, Study UID: ${this.requestParams.studyUID}` - }; - } - - async getZipOfSeriesDICOMFiles() { - let imagesPathList = await SeriesModel.getPathGroupOfInstances(this.requestParams); - if (imagesPathList.length > 0) { - this.setHeaders(this.seriesUID); - - let folders = []; - let seriesPath = path.dirname(imagesPathList[0].instancePath); - folders.push(seriesPath); - return await toZip(this.res, folders); - } - return { - status: false, - code: 404, - message: `not found, Series UID: ${this.requestParams.seriesUID}, Study UID: ${this.requestParams.studyUID}` - }; - } - - async getZipOfInstanceDICOMFile() { - let imagePath = await InstanceModel.getPathOfInstance(this.requestParams); - if (imagePath) { - this.setHeaders(this.instanceUID); - - return await toZip(this.res, [], imagePath.instancePath); - } - return { - status: false, - code: 404, - message: `not found, Instance UID: ${this.requestParams.instanceUID}, Series UID: ${this.requestParams.seriesUID}, Study UID: ${this.requestParams.studyUID}` - }; - } -} - -function toZip(res, folders = [], filename = "") { - return new Promise((resolve) => { - let archive = archiver('zip', { - gzip: true, - zlib: { level: 9 } // Sets the compression level. - }); - archive.on('error', function (err) { - console.error(err); - resolve({ - status: false, - code: 500, - data: err - }); - }); - archive.pipe(res); - if (folders.length > 0) { - for (let i = 0; i < folders.length; i++) { - let folderName = path.basename(folders[i]); - //archive.append(null, {name : folderName}); - archive.glob("*.dcm", { cwd: folders[i] }, { prefix: folderName }); - } - } else { - archive.file(filename); - } - archive.finalize().then(() => { - resolve({ - status: true, - code: 200, - data: "success" - }); - }); - }); -} - -module.exports.WADOZip = WADOZip; diff --git a/api-sql/dicom-web/wado-rs-instance.route.js b/api-sql/dicom-web/wado-rs-instance.route.js deleted file mode 100644 index c3655977..00000000 --- a/api-sql/dicom-web/wado-rs-instance.route.js +++ /dev/null @@ -1,70 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi } = require("@root/api/validator"); -const router = express(); - -//#region WADO-RS Retrieve Transaction Instance Resources - -/** - * @openapi - * /dicom-web/studies/{studyUID}: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's instances - * parameters: - * - $ref: "#/components/parameters/studyUID" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedDICOM" - * - */ -router.get( - "/studies/:studyUID", - require("./controller/WADO-RS/retrieveStudyInstances") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's series' instances - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedDICOM" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID", - require("./controller/WADO-RS/retrieveStudy-Series-Instances") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's instances - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedDICOM" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID", - require("./controller/WADO-RS/retrieveInstance") -); - -//#endregion - -module.exports = router; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/base.controller.js b/api/dicom-web/controller/WADO-RS/base.controller.js index 7636a755..0cb72f13 100644 --- a/api/dicom-web/controller/WADO-RS/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/base.controller.js @@ -3,7 +3,15 @@ const { RetrieveAuditService } = require("./service/retrieveAudit.service"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { WADOZip } = require("./service/WADOZip"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { sendNotSupportedMediaType, getAcceptType, supportInstanceMultipartType, ImageMultipartWriter, InstanceImagePathFactory, multipartContentTypeWriter, StudyImagePathFactory, SeriesImagePathFactory } = require("./service/WADO-RS.service"); +const { + sendNotSupportedMediaType, + getAcceptType, + supportInstanceMultipartType, + ImageMultipartWriter, + InstanceImagePathFactory, + multipartContentTypeWriter, + StudyImagePathFactory, + SeriesImagePathFactory } = require("@wado-rs-service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseRetrieveController extends Controller { @@ -115,7 +123,7 @@ class BaseMultipartRelatedResponseHandler { let imageMultipartWriter = new ImageMultipartWriter( this.request, this.response, - this.imagePathFactoryType , + this.imagePathFactoryType, multipartContentTypeWriter[type] ); diff --git a/api/dicom-web/controller/WADO-RS/service/WADOZip.js b/api/dicom-web/controller/WADO-RS/service/WADOZip.js index ab31df6f..afadc23b 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADOZip.js +++ b/api/dicom-web/controller/WADO-RS/service/WADOZip.js @@ -1,7 +1,7 @@ -const mongoose = require("mongoose"); const archiver = require("archiver"); -const wadoService = require("./WADO-RS.service"); const path = require("path"); +const { StudyModel } = require("@dbModels/study.model"); +const { SeriesModel } = require("@dbModels/series.model"); const { InstanceModel } = require("@dbModels/instance.model"); class WADOZip { constructor(iReq, iRes) { @@ -19,7 +19,7 @@ class WADOZip { } async getZipOfStudyDICOMFiles() { - let imagesPathList = await mongoose.model("dicomStudy").getPathGroupOfInstances(this.requestParams); + let imagesPathList = await StudyModel.getPathGroupOfInstances(this.requestParams); if (imagesPathList.length > 0) { this.setHeaders(this.studyUID); @@ -40,7 +40,7 @@ class WADOZip { } async getZipOfSeriesDICOMFiles() { - let imagesPathList = await mongoose.model("dicomSeries").getPathGroupOfInstances(this.requestParams); + let imagesPathList = await SeriesModel.getPathGroupOfInstances(this.requestParams); if (imagesPathList.length > 0) { this.setHeaders(this.seriesUID); diff --git a/routes.js b/routes.js index 7aa15c6f..8e4194e5 100644 --- a/routes.js +++ b/routes.js @@ -21,7 +21,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/stow-rs.route")); app.use("/dicom-web", require("./api/dicom-web/qido-rs.route")); - app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-instance.route")); + app.use("/dicom-web", require("./api/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); From bc73c402f685ea06208b6e77371c410404af27f8 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 21:13:38 +0800 Subject: [PATCH 162/365] refactor: use module alias for delete service --- .../controller/WADO-RS/deletion/instance.js | 55 ------------------- .../controller/WADO-RS/deletion/series.js | 54 ------------------ .../controller/WADO-RS/deletion/study.js | 54 ------------------ api-sql/dicom-web/delete.route.js | 17 ------ .../WADO-RS/deletion/base.controller.js | 4 +- config/jsconfig.sql.json | 3 +- config/modula-alias/sql/package.json | 3 +- routes.js | 2 +- 8 files changed, 6 insertions(+), 186 deletions(-) delete mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/instance.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/series.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/study.js delete mode 100644 api-sql/dicom-web/delete.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/instance.js b/api-sql/dicom-web/controller/WADO-RS/deletion/instance.js deleted file mode 100644 index f752ab9d..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/deletion/instance.js +++ /dev/null @@ -1,55 +0,0 @@ -const mongoose = require("mongoose"); -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { DeleteService } = require("./service/delete"); -const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); -const { NotFoundInstanceError } = require("@error/dicom-instance"); - -class DeleteInstanceController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let deleteService = new DeleteService(this.request, this.response, "instance"); - - try { - await deleteService.delete(); - - return this.response.status(200).json({ - Details: `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}, SOPInstanceUID: ${this.request.params.instanceUID}`, - HttpStatus: 200, - Message: "Delete Successful", - Method: "DELETE" - }); - } catch(e) { - - if (e instanceof NotFoundInstanceError) { - return this.response.status(404).json( - getNotFoundErrorMessage(e.message) - ); - } - - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let deleteStudyController = new DeleteInstanceController(req, res); - - await deleteStudyController.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/series.js b/api-sql/dicom-web/controller/WADO-RS/deletion/series.js deleted file mode 100644 index c82f0921..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/deletion/series.js +++ /dev/null @@ -1,54 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { DeleteService } = require("./service/delete"); -const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); -const { NotFoundInstanceError } = require("@error/dicom-instance"); - -class DeleteSeriesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let deleteService = new DeleteService(this.request, this.response, "series"); - - try { - await deleteService.delete(); - - return this.response.status(200).json({ - Details: `Delete Series permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}`, - HttpStatus: 200, - Message: "Delete Successful", - Method: "DELETE" - }); - } catch(e) { - - if (e instanceof NotFoundInstanceError) { - return this.response.status(404).json( - getNotFoundErrorMessage(e.message) - ); - } - - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let deleteStudyController = new DeleteSeriesController(req, res); - - await deleteStudyController.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/study.js b/api-sql/dicom-web/controller/WADO-RS/deletion/study.js deleted file mode 100644 index 6fb34ea2..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/deletion/study.js +++ /dev/null @@ -1,54 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { DeleteService } = require("./service/delete"); -const { getInternalServerErrorMessage, getNotFoundErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); -const { NotFoundInstanceError } = require("@error/dicom-instance"); - -class DeleteStudyController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let deleteService = new DeleteService(this.request, this.response, "study"); - - try { - await deleteService.delete(); - - return this.response.status(200).json({ - Details: `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}`, - HttpStatus: 200, - Message: "Delete Successful", - Method: "DELETE" - }); - } catch(e) { - - if (e instanceof NotFoundInstanceError) { - return this.response.status(404).json( - getNotFoundErrorMessage(e.message) - ); - } - - return this.response.status(500).json( - getInternalServerErrorMessage("An exception occur") - ); - } - - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let deleteStudyController = new DeleteStudyController(req, res); - - await deleteStudyController.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/delete.route.js b/api-sql/dicom-web/delete.route.js deleted file mode 100644 index 4eb02a06..00000000 --- a/api-sql/dicom-web/delete.route.js +++ /dev/null @@ -1,17 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi } = require("@root/api/validator"); -const router = express(); - - -//#region Delete - -router.delete("/studies/:studyUID", require("./controller/WADO-RS/deletion/study")); - -router.delete("/studies/:studyUID/series/:seriesUID", require("./controller/WADO-RS/deletion/series")); - -router.delete("/studies/:studyUID/series/:seriesUID/instances/:instanceUID", require("./controller/WADO-RS/deletion/instance")); - -//#endregion - -module.exports = router; diff --git a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js index 5d455f6f..6243a569 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js @@ -1,8 +1,6 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { DeleteService } = require("./service/delete"); -const { NotFoundInstanceError } = require("@error/dicom-instance"); -const { getNotFoundErrorMessage, getInternalServerErrorMessage } = require("@root/utils/errorResponse/errorResponseMessage"); +const { DeleteService } = require("@delete-service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseDeleteController extends Controller { diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index d910ba64..5dac951b 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -13,7 +13,8 @@ "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"], "@qido-rs-service": ["./api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], "@wado-rs-service": ["./api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], - "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"] + "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], + "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"] } }, "exclude": [ diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index 60df36b3..ec12c1ed 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -12,6 +12,7 @@ "@query-dicom-json-factory": "../../../api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js", "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", "@wado-rs-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", - "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js" + "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", + "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js" } } \ No newline at end of file diff --git a/routes.js b/routes.js index 8e4194e5..b26f5228 100644 --- a/routes.js +++ b/routes.js @@ -26,7 +26,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); - app.use("/dicom-web", require("./api-sql/dicom-web/delete.route")); + app.use("/dicom-web", require("./api/dicom-web/delete.route")); // app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); app.use("/wado", require("./api-sql/WADO-URI")); From 4425a570aba3bc305252fe637a31c608b8022f86 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 21:18:25 +0800 Subject: [PATCH 163/365] refactor: use module alias for metadata APIs --- .../controller/WADO-RS/metadata/base.controller.js | 2 +- .../WADO-RS/metadata/retrieveInstanceMetadata.js | 2 +- .../WADO-RS/metadata/retrieveSeriesMetadata.js | 10 +--------- .../WADO-RS/metadata/retrieveStudyMetadata.js | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js index 9c91e8a8..c5cf7f9f 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js @@ -1,6 +1,6 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { StudyImagePathFactory } = require("@wado-rs-service"); const { MetadataService } = require("../service/metadata.service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js index e5945b4a..61f0b21e 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js @@ -1,5 +1,5 @@ const { BaseRetrieveMetadataController } = require("./base.controller"); -const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); +const { InstanceImagePathFactory } = require("@wado-rs-service"); class RetrieveInstanceMetadataController extends BaseRetrieveMetadataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js index e79c2465..b44a26c5 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js @@ -1,13 +1,5 @@ -const mongoose = require("mongoose"); -const _ = require("lodash"); -const fs = require("fs"); -const path = require("path"); -const fileExist = require("../../../../../utils/file/fileExist"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); const { BaseRetrieveMetadataController } = require("./base.controller"); -const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); +const { SeriesImagePathFactory } = require("@wado-rs-service"); class RetrieveSeriesMetadataController extends BaseRetrieveMetadataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js index 6c171136..d8658ec9 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js @@ -1,4 +1,4 @@ -const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { StudyImagePathFactory } = require("@wado-rs-service"); const { BaseRetrieveMetadataController } = require("./base.controller"); class RetrieveStudyMetadataController extends BaseRetrieveMetadataController { From caa4fa0b29b390b403395c766603ea39d0fc7704 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 21:40:18 +0800 Subject: [PATCH 164/365] refactor: use module alias for rendered service --- .../metadata/retrieveInstanceMetadata.js | 2 +- .../metadata/retrieveSeriesMetadata.js | 2 +- .../WADO-RS/metadata/retrieveStudyMetadata.js | 2 +- .../WADO-RS/rendered/instanceFrames.js | 71 --------- .../controller/WADO-RS/rendered/instances.js | 60 ------- .../controller/WADO-RS/rendered/series.js | 55 ------- .../controller/WADO-RS/rendered/study.js | 64 -------- .../WADO-RS/service/thumbnail.service.js | 2 +- api-sql/dicom-web/wado-rs-rendered.route.js | 146 ------------------ .../WADO-RS/rendered/base.controller.js | 10 +- .../WADO-RS/rendered/instanceFrames.js | 4 +- .../controller/WADO-RS/rendered/instances.js | 4 +- .../controller/WADO-RS/rendered/series.js | 4 +- .../controller/WADO-RS/rendered/study.js | 4 +- .../WADO-RS/service/thumbnail.service.js | 2 +- .../WADO-RS/thumbnail/base.controller.js | 2 +- config/jsconfig.sql.json | 3 +- config/modula-alias/sql/package.json | 3 +- routes.js | 2 +- 19 files changed, 24 insertions(+), 418 deletions(-) delete mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/instances.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/series.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/rendered/study.js delete mode 100644 api-sql/dicom-web/wado-rs-rendered.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js index 8b47b5d1..e61d4b9c 100644 --- a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js +++ b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const fs = require("fs"); const path = require("path"); const fileExist = require("@root/utils/file/fileExist"); -const wadoService = require("../service/WADO-RS.service"); +const wadoService = require("@wado-rs-service"); const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js index ecfb2905..4f9d9419 100644 --- a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js +++ b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const fs = require("fs"); const path = require("path"); const fileExist = require("@root/utils/file/fileExist"); -const wadoService = require("../service/WADO-RS.service"); +const wadoService = require("@wado-rs-service"); const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js index 16a7bcf0..51f555de 100644 --- a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js +++ b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const fs = require("fs"); const path = require("path"); const fileExist = require("@root/utils/file/fileExist"); -const wadoService = require("../service/WADO-RS.service"); +const wadoService = require("@wado-rs-service"); const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js b/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js deleted file mode 100644 index d433035b..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/instanceFrames.js +++ /dev/null @@ -1,71 +0,0 @@ -const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); -const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class RetrieveRenderedInstanceFramesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - - let { - studyUID, - seriesUID, - instanceUID, - frameNumber - } = this.request.params; - - this.apiLogger.logger.info(`Get study's series' rendered instances' frames, study UID: ${studyUID}, series UID: ${seriesUID}, instance UID: ${instanceUID}, frame: ${frameNumber}`); - - let headerAccept = _.get(this.request.headers, "accept", ""); - if (!headerAccept.includes("*/*") && !headerAccept.includes("image/jpeg")) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow */* or image/jpeg , exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - InstanceImagePathFactory, - renderedService.InstanceFramesListWriter - ); - - let buffer = await renderedImageMultipartWriter.write(); - - this.apiLogger.logger.info(`Get instance's frame successfully, instance UID: ${instanceUID}, frame number: ${JSON.stringify(frameNumber)}`); - - if (buffer instanceof Buffer) { - return this.response.end(buffer, "binary"); - } - - return this.response.end(); - } catch(e) { - console.error(e); - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e, Object.getOwnPropertyNames(e), 4), "utf8"); - } - } -} -/** - * - * @param {import("http").incomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedInstanceFramesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js b/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js deleted file mode 100644 index 0ceb5599..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/instances.js +++ /dev/null @@ -1,60 +0,0 @@ -const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); -const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class RetrieveRenderedInstancesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - let headerAccept = _.get(this.request.headers, "accept", ""); - - apiLogger.logger.info(`Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); - - if (!headerAccept == `multipart/related; type="image/jpeg"`) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - InstanceImagePathFactory, - renderedService.InstanceFramesWriter - ); - - await renderedImageMultipartWriter.write(); - - apiLogger.logger.info(`Write Multipart Successfully, study's series' instances' rendered images, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}, instance UID: ${this.request.params.instanceUID}`); - return this.response.end(); - } catch(e) { - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e)); - } - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedInstancesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/series.js b/api-sql/dicom-web/controller/WADO-RS/rendered/series.js deleted file mode 100644 index ef2d68b6..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/series.js +++ /dev/null @@ -1,55 +0,0 @@ -const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); -const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { logger } = require("@root/utils/logs/log"); -const { Controller } = require("@root/api/controller.class"); - -class RetrieveRenderedSeriesController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let headerAccept = _.get(this.request.headers, "accept", ""); - logger.info(`[WADO-RS] [Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); - if (!headerAccept == `multipart/related; type="image/jpeg"`) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - SeriesImagePathFactory, - renderedService.SeriesFramesWriter - ); - - await renderedImageMultipartWriter.write(); - - logger.info(`[WADO-RS] [path: ${this.request.originalUrl}] [Write Multipart Successfully, study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); - - return this.response.end(); - } catch(e) { - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e)); - } - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedSeriesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/WADO-RS/rendered/study.js b/api-sql/dicom-web/controller/WADO-RS/rendered/study.js deleted file mode 100644 index 4cefe7d5..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/rendered/study.js +++ /dev/null @@ -1,64 +0,0 @@ -const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); -const { - StudyImagePathFactory -} = require("../service/WADO-RS.service"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { Controller } = require("@root/api/controller.class"); - -class RetrieveRenderedStudyController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let headerAccept = _.get(this.request.headers, "accept", ""); - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get study's rendered instances, study UID: ${this.request.params.studyUID}`); - - if (!headerAccept == `multipart/related; type="image/jpeg"`) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(badRequestMessage)); - } - - try { - - let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( - this.request, - this.response, - StudyImagePathFactory, - renderedService.StudyFramesWriter - ); - - await renderedImageMultipartWriter.write(); - - apiLogger.logger.info(`Write Multipart Successfully, study's rendered instances, study UID: ${this.request.params.studyUID}`); - - return this.response.end(); - } catch(e) { - apiLogger.logger.error(e); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(e)); - } - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedStudyController(req, res); - - await controller.doPipeline(); -}; diff --git a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 07c10cd3..fb7d7fa2 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,5 +1,5 @@ const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const renderedService = require("../service/rendered.service"); +const renderedService = require("@rendered-service"); const _ = require("lodash"); const { ThumbnailService, diff --git a/api-sql/dicom-web/wado-rs-rendered.route.js b/api-sql/dicom-web/wado-rs-rendered.route.js deleted file mode 100644 index ff01ef88..00000000 --- a/api-sql/dicom-web/wado-rs-rendered.route.js +++ /dev/null @@ -1,146 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi } = require("@root/api/validator"); -const router = express(); - - -//#region WADO-RS Retrieve Transaction Rendered Resources - -const renderedQueryValidation = { - quality: Joi.number().integer().min(1).max(100), - iccprofile: Joi.string().default("no").valid("no", "yes", "srgb", "adobergb", "rommrgb"), - viewport: Joi.string().custom((v, helper) => { - let valueSplit = v.split(","); - if (valueSplit.length == 2) { - let [vw, vh] = valueSplit; - if (!Joi.number().min(0).validate(vw).error && - !Joi.number().min(0).validate(vh).error) { - return v; - } - return helper.message(`invalid viewport parameter, viewport=vw,vh. The vw and vh must be number`); - } else if (valueSplit.length == 6) { - let [vw, vh, sx, sy, sw, sh] = valueSplit; - if (Joi.number().empty("").validate(sx).error) { - return helper.message("invalid viewport parameter, sx must be number"); - } else if (Joi.number().empty("").validate(sy).error) { - return helper.message("invalid viewport parameter, sy must be number"); - } - [vw, vh, sx, sy, sw, sh] = valueSplit.map(v=> Number(v)); - if (!Joi.number().min(0).validate(vw).error && - !Joi.number().min(0).validate(vh).error && - !Joi.number().min(0).validate(sx).error && - !Joi.number().min(0).validate(sy).error && - !Joi.number().validate(sw).error && - !Joi.number().validate(sh).error - ) { - return v; - } - } - return helper.message("invalid viewport parameter, viewport=vw,vh or viewport=vw,vh,sx,sy,sw,sh"); - }) -}; - -/** - * @openapi - * /dicom-web/studies/{studyUID}/rendered: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's rendered images - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/quality" - * - $ref: "#/components/parameters/viewport" - * - $ref: "#/components/parameters/iccprofile" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedImageJpeg" - * - */ -router.get( - "/studies/:studyUID/rendered", - validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/study") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/rendered: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's Series' rendered images - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/quality" - * - $ref: "#/components/parameters/viewport" - * - $ref: "#/components/parameters/iccprofile" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedImageJpeg" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/rendered", - validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/series") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/rendered: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's Series' instance's rendered images - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * - $ref: "#/components/parameters/quality" - * - $ref: "#/components/parameters/viewport" - * - $ref: "#/components/parameters/iccprofile" - * responses: - * 200: - * $ref: "#/components/responses/MultipartRelatedImageJpeg" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/rendered", - validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/instances") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/frames/{frameNumbers}/rendered: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's rendered images - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * - $ref: "#/components/parameters/frameNumbers" - * - $ref: "#/components/parameters/quality" - * - $ref: "#/components/parameters/viewport" - * - $ref: "#/components/parameters/iccprofile" - * responses: - * 200: - * $ref: "#/components/responses/RetrieveRenderedByFrameNumbers" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/frames/:frameNumber/rendered", - validateParams({ - frameNumber : intArrayJoi.intArray().items(Joi.number().integer().min(1)).single() - } , "params" , {allowUnknown : true}), - validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/instanceFrames") -); - -//#endregion - -module.exports = router; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js index 5594de04..71f9e6af 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js @@ -1,10 +1,10 @@ const _ = require("lodash"); -const renderedService = require("../service/rendered.service"); +const renderedService = require("@rendered-service"); const { - StudyImagePathFactory, SeriesImagePathFactory, InstanceImagePathFactory -} = require("../service/WADO-RS.service"); -const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); + StudyImagePathFactory +} = require("@wado-rs-service"); +const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); const { Controller } = require("../../../../controller.class"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); diff --git a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js b/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js index d40604ad..eee25b09 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js +++ b/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js @@ -1,7 +1,7 @@ const _ = require("lodash"); -const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); +const { InstanceImagePathFactory } = require("@wado-rs-service"); const { BaseRetrieveRenderedController } = require("./base.controller"); -const { InstanceFramesListWriter } = require("../service/rendered.service"); +const { InstanceFramesListWriter } = require("@rendered-service"); class RetrieveRenderedInstanceFramesController extends BaseRetrieveRenderedController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/rendered/instances.js b/api/dicom-web/controller/WADO-RS/rendered/instances.js index 7405906e..46324f05 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/instances.js +++ b/api/dicom-web/controller/WADO-RS/rendered/instances.js @@ -1,5 +1,5 @@ -const { InstanceImagePathFactory } = require("../service/WADO-RS.service"); -const { InstanceFramesWriter } = require("../service/rendered.service"); +const { InstanceImagePathFactory } = require("@wado-rs-service"); +const { InstanceFramesWriter } = require("@rendered-service"); const { BaseRetrieveRenderedController } = require("./base.controller"); class RetrieveRenderedInstancesController extends BaseRetrieveRenderedController { diff --git a/api/dicom-web/controller/WADO-RS/rendered/series.js b/api/dicom-web/controller/WADO-RS/rendered/series.js index 404c5ef6..92a1e1d2 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/series.js +++ b/api/dicom-web/controller/WADO-RS/rendered/series.js @@ -1,5 +1,5 @@ -const { SeriesImagePathFactory } = require("../service/WADO-RS.service"); -const { SeriesFramesWriter } = require("../service/rendered.service"); +const { SeriesImagePathFactory } = require("@wado-rs-service"); +const { SeriesFramesWriter } = require("@rendered-service"); const { BaseRetrieveRenderedController } = require("./base.controller"); class RetrieveRenderedSeriesController extends BaseRetrieveRenderedController { diff --git a/api/dicom-web/controller/WADO-RS/rendered/study.js b/api/dicom-web/controller/WADO-RS/rendered/study.js index 004b19da..84bc6f36 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/study.js +++ b/api/dicom-web/controller/WADO-RS/rendered/study.js @@ -1,5 +1,5 @@ -const { StudyImagePathFactory } = require("../service/WADO-RS.service"); -const { StudyFramesWriter } = require("../service/rendered.service"); +const { StudyImagePathFactory } = require("@wado-rs-service"); +const { StudyFramesWriter } = require("@rendered-service"); const { BaseRetrieveRenderedController } = require("./base.controller"); class RetrieveRenderedStudyController extends BaseRetrieveRenderedController { diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 4bed2b02..82937497 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,6 +1,6 @@ const { InstanceModel } = require("@dbModels/instance.model"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const renderedService = require("../service/rendered.service"); +const renderedService = require("@rendered-service"); const _ = require("lodash"); const { getUidsString } = require("./WADO-RS.service"); class ThumbnailService { diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js index 1c73f9d8..e09c1fd3 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js @@ -1,5 +1,5 @@ const { Controller } = require("@root/api/controller.class"); -const { StudyImagePathFactory } = require("../service/WADO-RS.service"); +const { StudyImagePathFactory } = require("@wado-rs-service"); const { ThumbnailService } = require("../service/thumbnail.service"); const { ApiLogger } = require("@root/utils/logs/api-logger"); diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index 5dac951b..03546ccd 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -14,7 +14,8 @@ "@qido-rs-service": ["./api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], "@wado-rs-service": ["./api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], - "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"] + "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"], + "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"] } }, "exclude": [ diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index ec12c1ed..30bc0646 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -13,6 +13,7 @@ "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", "@wado-rs-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", - "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js" + "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js", + "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js" } } \ No newline at end of file diff --git a/routes.js b/routes.js index b26f5228..cc3f9cfb 100644 --- a/routes.js +++ b/routes.js @@ -23,7 +23,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-instance.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); - app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-rendered.route")); + app.use("/dicom-web", require("./api/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); From abc07026583616bdd0a0bdf3698141ca1f95b53d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 21:53:53 +0800 Subject: [PATCH 165/365] refactor: module alias for thumbnail APIs refactors the module alias for the thumbnail APIs in the WADO-RS service. It updates the require statements to use the new module alias in the thumbnail.service.js file. Additionally, it removes unnecessary code related to retrieving frame and instance thumbnails from the thumbnail folder. - With module alias, we don't need sql controller anymore --- .../metadata/retrieveInstanceMetadata.js | 65 ---- .../metadata/retrieveSeriesMetadata.js | 68 ---- .../WADO-RS/metadata/retrieveStudyMetadata.js | 71 ---- .../WADO-RS/service/thumbnail.service.js | 171 ++-------- .../controller/WADO-RS/thumbnail/frame.js | 52 --- .../controller/WADO-RS/thumbnail/instance.js | 51 --- .../controller/WADO-RS/thumbnail/series.js | 50 --- .../controller/WADO-RS/thumbnail/study.js | 49 --- api-sql/dicom-web/qido-rs.route.js | 311 ------------------ api-sql/dicom-web/stow-rs.route.js | 35 -- api-sql/dicom-web/wado-rs-metadata.route.js | 71 ---- api-sql/dicom-web/wado-rs-thumbnail.route.js | 118 ------- config/jsconfig.sql.json | 3 +- config/modula-alias/sql/package.json | 3 +- routes.js | 4 +- 15 files changed, 30 insertions(+), 1092 deletions(-) delete mode 100644 api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js delete mode 100644 api-sql/dicom-web/qido-rs.route.js delete mode 100644 api-sql/dicom-web/stow-rs.route.js delete mode 100644 api-sql/dicom-web/wado-rs-metadata.route.js delete mode 100644 api-sql/dicom-web/wado-rs-thumbnail.route.js diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js deleted file mode 100644 index e61d4b9c..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js +++ /dev/null @@ -1,65 +0,0 @@ -const _ = require("lodash"); -const fs = require("fs"); -const path = require("path"); -const fileExist = require("@root/utils/file/fileExist"); -const wadoService = require("@wado-rs-service"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { InstanceModel } = require("@models/sql/models/instance.model"); - -class RetrieveInstanceMetadataController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - - apiLogger.addTokenValue(); - apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instance Metadata] [instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); - try { - let responseMetadata = []; - - let imagePathObj = await InstanceModel.getPathOfInstance(this.request.params); - if (imagePathObj) { - let instanceDir = path.dirname(imagePathObj.instancePath); - let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); - if (await fileExist(metadataPath)) { - let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); - let metadataJson = JSON.parse(metadataJsonStr); - wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); - responseMetadata.push(metadataJson); - } - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(responseMetadata)); - } - - this.response.writeHead(204); - return this.response.end(JSON.stringify( - errorResponse.getNotFoundErrorMessage( - `Not found metadata of instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}` - ) - )); - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - console.error(errorStr); - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - } - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveInstanceMetadataController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js deleted file mode 100644 index 4f9d9419..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js +++ /dev/null @@ -1,68 +0,0 @@ -const _ = require("lodash"); -const fs = require("fs"); -const path = require("path"); -const fileExist = require("@root/utils/file/fileExist"); -const wadoService = require("@wado-rs-service"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { SeriesModel } = require("@models/sql/models/series.model"); - -class RetrieveSeriesMetadataController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - - apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instances Metadata] [series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); - try { - let responseMetadata = []; - - let imagesPathList = await SeriesModel.getPathGroupOfInstances(this.request.params); - if (imagesPathList.length > 0) { - for (let imagePathObj of imagesPathList) { - let instanceDir = path.dirname(imagePathObj.instancePath); - let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); - if (await fileExist(metadataPath)) { - let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); - let metadataJson = JSON.parse(metadataJsonStr); - wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); - responseMetadata.push(metadataJson); - } - } - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(responseMetadata)); - } - - this.response.writeHead(204); - return this.response.end(JSON.stringify( - errorResponse.getNotFoundErrorMessage( - `Not found metadata of series UID:${this.request.params.seriesUID} study UID: ${this.request.params.studyUID}` - ) - )); - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - console.error(errorStr); - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - } - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveSeriesMetadataController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js b/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js deleted file mode 100644 index 51f555de..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js +++ /dev/null @@ -1,71 +0,0 @@ -const _ = require("lodash"); -const fs = require("fs"); -const path = require("path"); -const fileExist = require("@root/utils/file/fileExist"); -const wadoService = require("@wado-rs-service"); -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { StudyModel } = require("@models/sql/models/study.model"); - -class RetrieveStudyMetadataController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Instances Metadata [study UID: ${this.request.params.studyUID}]`); - - try { - let responseMetadata = []; - - let pathGroupOfInstancesInStudy = await StudyModel.getPathGroupOfInstances(this.request.params); - - if (pathGroupOfInstancesInStudy.length > 0) { - - for (let imagePathObj of pathGroupOfInstancesInStudy) { - let instanceDir = path.dirname(imagePathObj.instancePath); - let metadataPath = path.join(instanceDir, `${imagePathObj.instanceUID}.metadata.json`); - if (await fileExist(metadataPath)) { - let metadataJsonStr = fs.readFileSync(metadataPath, { encoding: "utf-8" }); - let metadataJson = JSON.parse(metadataJsonStr); - wadoService.addHostnameOfBulkDataUrl(metadataJson, this.request); - responseMetadata.push(metadataJson); - } - } - - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(responseMetadata)); - } - - this.response.writeHead(204); - return this.response.end(); - - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(); - } - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveStudyMetadataController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js index fb7d7fa2..1e635bc4 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,156 +1,33 @@ -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); const renderedService = require("@rendered-service"); const _ = require("lodash"); -const { - ThumbnailService, - StudyThumbnailFactory, - SeriesThumbnailFactory, - InstanceThumbnailFactory +const { + ThumbnailService, + StudyThumbnailFactory, + SeriesThumbnailFactory, + InstanceThumbnailFactory } = require("@root/api/dicom-web/controller/WADO-RS/service/thumbnail.service"); -const { InstanceModel } = require("@models/sql/models/instance.model"); -const { getUidsString } = require("@root/api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); -class SqlThumbnailService extends ThumbnailService { - /** - * - * @param {import("express").Request} req - * @param {import("express").Response} res - * @param {typeof ThumbnailFactory} thumbnailFactory - */ - constructor(req, res, apiLogger, thumbnailFactory) { - super(req, res, apiLogger, thumbnailFactory); +ThumbnailService.prototype.getThumbnailByInstance = async function (instanceFramesObj) { + let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); + dicomNumberOfFrames = parseInt(dicomNumberOfFrames); + let medianFrame = 1; + if (dicomNumberOfFrames > 1) medianFrame = dicomNumberOfFrames >> 1; + if (this.request.params.frameNumber) { + medianFrame = this.request.params.frameNumber[0]; } - async getThumbnailAndResponse() { - if (!_.get(this.request, "query.viewport")) { - _.set(this.request, "query.viewport", "100,100"); - } - - let instanceFramesObj = await this.thumbnailFactory.getThumbnailInstance(); - if (this.checkInstanceExists(instanceFramesObj)) { - return; - } - - let thumbnail = await this.getThumbnailByInstance(instanceFramesObj); - if (thumbnail) { - return this.response.end(thumbnail, "binary"); - } - throw new Error(`Can not process this image, instanceUID: ${instanceFramesObj.instanceUID}`); - } - - async getThumbnailByInstance(instanceFramesObj) { - let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); - dicomNumberOfFrames = parseInt(dicomNumberOfFrames); - let medianFrame = 1; - if (dicomNumberOfFrames > 1) medianFrame = dicomNumberOfFrames >> 1; - if (this.request.params.frameNumber) { - medianFrame = this.request.params.frameNumber[0]; - } - - let postProcessResult = await renderedService.postProcessFrameImage(this.request, medianFrame, instanceFramesObj); - if (postProcessResult.status) { - this.response.writeHead(200, { - "Content-Type": "image/jpeg" - }); - this.apiLogger.logger.info(`Get instance's thumbnail successfully, instance UID: ${instanceFramesObj.instanceUID}`); - return postProcessResult.magick.toBuffer(); - } - return undefined; - } - - checkInstanceExists(instanceFramesObj) { - if (!instanceFramesObj) { - this.response.writeHead(404, { - "Content-Type": "application/dicom+json" - }); - let notFoundMessage = errorResponse.getNotFoundErrorMessage(`Not Found, ${getUidsString(this.thumbnailFactory.uids)}`); - - let notFoundMessageStr = JSON.stringify(notFoundMessage); - - this.apiLogger.logger.warn(`[${notFoundMessageStr}]`); - - return this.response.end(notFoundMessageStr); - } - return undefined; - } -} - - -class SqlStudyThumbnailFactory extends StudyThumbnailFactory { - constructor(uids) { - super(uids); - } - - /** - * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids - */ - async getThumbnailInstance() { - let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ - studyUID: this.uids.studyUID - }); - if (!medianInstance) return undefined; - - let instanceFramesObj = await renderedService.getInstanceFrameObj({ - studyUID: this.uids.studyUID, - seriesUID: medianInstance.seriesUID, - instanceUID: medianInstance.instanceUID + let postProcessResult = await renderedService.postProcessFrameImage(this.request, medianFrame, instanceFramesObj); + if (postProcessResult.status) { + this.response.writeHead(200, { + "Content-Type": "image/jpeg" }); - - return instanceFramesObj; + this.apiLogger.logger.info(`Get instance's thumbnail successfully, instance UID: ${instanceFramesObj.instanceUID}`); + return postProcessResult.magick.toBuffer(); } + return undefined; +}; -} - -class SqlSeriesThumbnailFactory extends SeriesThumbnailFactory { - constructor(uids) { - super(uids); - } - - /** - * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids - */ - async getThumbnailInstance() { - let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ - studyUID: this.uids.studyUID, - seriesUID: this.uids.seriesUID - }); - if (!medianInstance) return undefined; - - let instanceFramesObj = await renderedService.getInstanceFrameObj({ - studyUID: this.uids.studyUID, - seriesUID: this.uids.seriesUID, - instanceUID: medianInstance.instanceUID - }); - - return instanceFramesObj; - } - -} - -class SqlInstanceThumbnailFactory extends InstanceThumbnailFactory { - constructor(uids) { - super(uids); - } - - /** - * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids - */ - async getThumbnailInstance() { - let instanceFramesObj = await renderedService.getInstanceFrameObj({ - studyUID: this.uids.studyUID, - seriesUID: this.uids.seriesUID, - instanceUID: this.uids.instanceUID - }); - - return instanceFramesObj; - } - -} - -module.exports.ThumbnailService = SqlThumbnailService; -module.exports.StudyThumbnailFactory = SqlStudyThumbnailFactory; -module.exports.SeriesThumbnailFactory = SqlSeriesThumbnailFactory; -module.exports.InstanceThumbnailFactory = SqlInstanceThumbnailFactory; \ No newline at end of file +module.exports.ThumbnailService = ThumbnailService; +module.exports.StudyThumbnailFactory = StudyThumbnailFactory; +module.exports.SeriesThumbnailFactory = SeriesThumbnailFactory; +module.exports.InstanceThumbnailFactory = InstanceThumbnailFactory; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js deleted file mode 100644 index 6727d720..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/thumbnail/frame.js +++ /dev/null @@ -1,52 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { - ThumbnailService, - InstanceThumbnailFactory -} = require("../service/thumbnail.service"); - - - -class RetrieveFrameThumbnailController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ - series UID: ${this.request.params.seriesUID}]\ - instance UID: ${this.request.params.instanceUID}\ - frames: ${JSON.stringify(this.request.params.frameNumber)}`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, InstanceThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveFrameThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js deleted file mode 100644 index 08877156..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/thumbnail/instance.js +++ /dev/null @@ -1,51 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { - ThumbnailService, - InstanceThumbnailFactory -} = require("../service/thumbnail.service"); - - - -class RetrieveInstanceThumbnailController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ - series UID: ${this.request.params.seriesUID}]\ - instance UID: ${this.request.params.instanceUID}`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, InstanceThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveInstanceThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js deleted file mode 100644 index fcb00e9f..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/thumbnail/series.js +++ /dev/null @@ -1,50 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { - ThumbnailService, - SeriesThumbnailFactory -} = require("../service/thumbnail.service"); - - - -class RetrieveSeriesThumbnailController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Series' Thumbnail [study UID: ${this.request.params.studyUID},\ - series UID: ${this.request.params.seriesUID}]`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, SeriesThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveSeriesThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js b/api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js deleted file mode 100644 index ad13ea8e..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/thumbnail/study.js +++ /dev/null @@ -1,49 +0,0 @@ -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { - ThumbnailService, - StudyThumbnailFactory -} = require("../service/thumbnail.service"); - - - -class RetrieveStudyThumbnailController extends Controller { - constructor(req, res) { - super(req, res); - } - - async mainProcess() { - - let apiLogger = new ApiLogger(this.request, "WADO-RS"); - apiLogger.addTokenValue(); - - apiLogger.logger.info(`Get Study's Thumbnail [study UID: ${this.request.params.studyUID}]`); - - try { - let thumbnailService = new ThumbnailService(this.request, this.response, apiLogger, StudyThumbnailFactory); - return thumbnailService.getThumbnailAndResponse(); - } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); - } - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveStudyThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/dicom-web/qido-rs.route.js b/api-sql/dicom-web/qido-rs.route.js deleted file mode 100644 index 584adc32..00000000 --- a/api-sql/dicom-web/qido-rs.route.js +++ /dev/null @@ -1,311 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi, stringArrayJoi } = require("@root/api/validator"); -const router = express(); -const { - dictionary -} = require("../../models/DICOM/dicom-tags-dic"); - -const KEYWORD_KEYS = Object.keys(dictionary.keyword); -const HEX_KEYS = Object.keys(dictionary.tag); - -//#region QIDO-RS -const queryValidation = { - limit: Joi.number().integer().min(1).max(100), - offset: Joi.number().integer().min(0), - includefield: stringArrayJoi.stringArray().items( - Joi.string().custom( - (attribute, helper) => { - if (!isValidAttribute(attribute)) { - return helper.message(`Invalid DICOM attribute: ${attribute}, please enter valid keyword or tag`); - } - return convertKeywordToHex(attribute); - } - ) - ).single() -}; - -/** - * - * @param {string} attribute - */ -function isValidAttribute(attribute) { - if (KEYWORD_KEYS.indexOf(attribute) >= 0 || - HEX_KEYS.indexOf(attribute) >= 0 || - attribute === "all") { - - return true; - } - - return false; -} - -function convertKeywordToHex(attribute) { - if (KEYWORD_KEYS.indexOf(attribute) >= 0) { - return dictionary.keyword[attribute]; - } - return attribute; -} - - -/** - * @openapi - * /dicom-web/studies: - * get: - * tags: - * - QIDO-RS - * description: Query for studies - * parameters: - * - $ref: "#/components/parameters/StudyDate" - * - $ref: "#/components/parameters/StudyTime" - * - $ref: "#/components/parameters/AccessionNumber" - * - $ref: "#/components/parameters/ModalitiesInStudy" - * - $ref: "#/components/parameters/ReferringPhysicianName" - * - $ref: "#/components/parameters/PatientName" - * - $ref: "#/components/parameters/PatientID" - * - $ref: "#/components/parameters/StudyID" - * responses: - * 200: - * description: Query successfully - * content: - * "application/dicom+json": - * schema: - * type: array - * items: - * allOf: - * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" - */ -router.get("/studies", validateParams(queryValidation, "query", { - allowUnknown: true -}), require("./controller/QIDO-RS/queryAllStudies")); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series: - * get: - * tags: - * - QIDO-RS - * description: Query for series from specific study's UID - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/StudyDate" - * - $ref: "#/components/parameters/StudyTime" - * - $ref: "#/components/parameters/AccessionNumber" - * - $ref: "#/components/parameters/ModalitiesInStudy" - * - $ref: "#/components/parameters/ReferringPhysicianName" - * - $ref: "#/components/parameters/PatientName" - * - $ref: "#/components/parameters/PatientID" - * - $ref: "#/components/parameters/StudyID" - * - $ref: "#/components/parameters/Modality" - * - $ref: "#/components/parameters/SeriesNumber" - * responses: - * 200: - * description: Query successfully - * content: - * "application/dicom+json": - * schema: - * type: array - * items: - * allOf: - * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" - * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" - */ -router.get( - "/studies/:studyUID/series", validateParams(queryValidation, "query", { - allowUnknown: true - }), - require("./controller/QIDO-RS/queryStudies-Series") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/instances: - * get: - * tags: - * - QIDO-RS - * description: Query for studies - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/StudyDate" - * - $ref: "#/components/parameters/StudyTime" - * - $ref: "#/components/parameters/AccessionNumber" - * - $ref: "#/components/parameters/ModalitiesInStudy" - * - $ref: "#/components/parameters/ReferringPhysicianName" - * - $ref: "#/components/parameters/PatientName" - * - $ref: "#/components/parameters/PatientID" - * - $ref: "#/components/parameters/StudyID" - * - $ref: "#/components/parameters/Modality" - * - $ref: "#/components/parameters/SeriesNumber" - * - $ref: "#/components/parameters/SOPClassUID" - * - $ref: "#/components/parameters/InstanceNumber" - * responses: - * 200: - * description: Query successfully - * content: - * "application/dicom+json": - * schema: - * type: array - * items: - * allOf: - * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" - * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" - * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" - */ -router.get( - "/studies/:studyUID/instances", validateParams(queryValidation, "query", { - allowUnknown: true - }), - require("./controller/QIDO-RS/queryStudies-Instances") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances: - * get: - * tags: - * - QIDO-RS - * description: Query for studies - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/StudyDate" - * - $ref: "#/components/parameters/StudyTime" - * - $ref: "#/components/parameters/AccessionNumber" - * - $ref: "#/components/parameters/ModalitiesInStudy" - * - $ref: "#/components/parameters/ReferringPhysicianName" - * - $ref: "#/components/parameters/PatientName" - * - $ref: "#/components/parameters/PatientID" - * - $ref: "#/components/parameters/StudyID" - * - $ref: "#/components/parameters/Modality" - * - $ref: "#/components/parameters/SeriesNumber" - * - $ref: "#/components/parameters/SOPClassUID" - * - $ref: "#/components/parameters/InstanceNumber" - * responses: - * 200: - * description: Query successfully - * content: - * "application/dicom+json": - * schema: - * type: array - * items: - * allOf: - * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" - * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" - * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances", validateParams(queryValidation, "query", { - allowUnknown: true - }), - require("./controller/QIDO-RS/queryStudies-Series-Instance") -); - -/** - * @openapi - * /dicom-web/series: - * get: - * tags: - * - QIDO-RS - * description: Query all series in server - * parameters: - * - $ref: "#/components/parameters/StudyDate" - * - $ref: "#/components/parameters/StudyTime" - * - $ref: "#/components/parameters/AccessionNumber" - * - $ref: "#/components/parameters/ModalitiesInStudy" - * - $ref: "#/components/parameters/ReferringPhysicianName" - * - $ref: "#/components/parameters/PatientName" - * - $ref: "#/components/parameters/PatientID" - * - $ref: "#/components/parameters/StudyID" - * - $ref: "#/components/parameters/Modality" - * - $ref: "#/components/parameters/SeriesNumber" - * responses: - * 200: - * description: Query successfully - * content: - * "application/dicom+json": - * schema: - * type: array - * items: - * allOf: - * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" - * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" - * - */ -router.get( - "/series", validateParams(queryValidation, "query", { - allowUnknown: true - }), - require("./controller/QIDO-RS/queryAllSeries") -); - -/** - * @openapi - * /dicom-web/instances: - * get: - * tags: - * - QIDO-RS - * description: Query all instances in server - * parameters: - * - $ref: "#/components/parameters/StudyDate" - * - $ref: "#/components/parameters/StudyTime" - * - $ref: "#/components/parameters/AccessionNumber" - * - $ref: "#/components/parameters/ModalitiesInStudy" - * - $ref: "#/components/parameters/ReferringPhysicianName" - * - $ref: "#/components/parameters/PatientName" - * - $ref: "#/components/parameters/PatientID" - * - $ref: "#/components/parameters/StudyID" - * - $ref: "#/components/parameters/Modality" - * - $ref: "#/components/parameters/SeriesNumber" - * - $ref: "#/components/parameters/SOPClassUID" - * - $ref: "#/components/parameters/InstanceNumber" - * responses: - * 200: - * description: Query successfully - * content: - * "application/dicom+json": - * schema: - * type: array - * items: - * allOf: - * - $ref: "#/components/schemas/StudyRequiredMatchingAttributes" - * - $ref: "#/components/schemas/SeriesRequiredMatchingAttributes" - * - $ref: "#/components/schemas/InstanceRequiredMatchingAttributes" - */ -router.get( - "/instances", validateParams(queryValidation, "query", { - allowUnknown: true - }), - require("./controller/QIDO-RS/queryAllInstances") -); - -/** - * @openapi - * /dicom-web/patients: - * get: - * tags: - * - QIDO-RS - * description: Query all patients in server - * parameters: - * - $ref: "#/components/parameters/PatientName" - * - $ref: "#/components/parameters/PatientID" - * - $ref: "#/components/parameters/PatientBirthDate" - * - $ref: "#/components/parameters/PatientBirthTime" - * responses: - * 200: - * description: Query successfully - * content: - * "application/dicom+json": - * schema: - * type: array - * items: - * allOf: - * - $ref: "#/components/schemas/PatientRequiredMatchingAttributes" - */ -router.get( - "/patients", - require("./controller/QIDO-RS/allPatient") -); - -//#endregion - -module.exports = router; \ No newline at end of file diff --git a/api-sql/dicom-web/stow-rs.route.js b/api-sql/dicom-web/stow-rs.route.js deleted file mode 100644 index 73b80eee..00000000 --- a/api-sql/dicom-web/stow-rs.route.js +++ /dev/null @@ -1,35 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi } = require("@root/api/validator"); -const router = express(); - -//#region STOW-RS - -/** - * @openapi - * /dicom-web/studies: - * post: - * tags: - * - STOW-RS - * description: store DICOM instance - * requestBody: - * content: - * multipart/related: - * schema: - * type: object - * properties: - * file: - * type: string - * format: binary - * encoding: - * file: - * contentType: application/dicom; - * responses: - * "200": - * description: The DICOM instance store successfully - */ -router.post("/studies", require("./controller/STOW-RS/storeInstance")); - -//#endregion - -module.exports = router; \ No newline at end of file diff --git a/api-sql/dicom-web/wado-rs-metadata.route.js b/api-sql/dicom-web/wado-rs-metadata.route.js deleted file mode 100644 index 9bd25200..00000000 --- a/api-sql/dicom-web/wado-rs-metadata.route.js +++ /dev/null @@ -1,71 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi } = require("@root/api/validator"); -const router = express(); - - -//#region WADO-RS Retrieve Transaction Metadata Resources - -/** - * @openapi - * /dicom-web/studies/{studyUID}/metadata: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's instances' metadata - * parameters: - * - $ref: "#/components/parameters/studyUID" - * responses: - * 200: - * $ref: "#/components/responses/DicomMetadata" - * - */ -router.get( - "/studies/:studyUID/metadata", - require("./controller/WADO-RS/metadata/retrieveStudyMetadata") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/metadata: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's series' instances' metadata - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * responses: - * 200: - * $ref: "#/components/responses/DicomMetadata" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/metadata", - require("./controller/WADO-RS/metadata/retrieveSeriesMetadata") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/metadata: - * get: - * tags: - * - WADO-RS - * description: Retrieve instance's metadata - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * responses: - * 200: - * $ref: "#/components/responses/DicomMetadata" - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/metadata", - require("./controller/WADO-RS/metadata/retrieveInstanceMetadata") -); - -//#endregion - -module.exports = router; \ No newline at end of file diff --git a/api-sql/dicom-web/wado-rs-thumbnail.route.js b/api-sql/dicom-web/wado-rs-thumbnail.route.js deleted file mode 100644 index 6e982d63..00000000 --- a/api-sql/dicom-web/wado-rs-thumbnail.route.js +++ /dev/null @@ -1,118 +0,0 @@ -const express = require("express"); -const Joi = require("joi"); -const { validateParams, intArrayJoi } = require("@root/api/validator"); -const router = express(); - - -//#region WADO-RS Retrieve Transaction Thumbnail Resources - -/** - * @openapi - * /dicom-web/studies/{studyUID}/thumbnail: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's instances' metadata - * parameters: - * - $ref: "#/components/parameters/studyUID" - * responses: - * 200: - * description: The response payload for WADO-RS Thumbnail - * content: - * "image/jpeg": - * schema: - * type: string - * format: binary - * - */ -router.get( - "/studies/:studyUID/thumbnail", - require("./controller/WADO-RS/thumbnail/study") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/thumbnail: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's series' thumbnail - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * responses: - * 200: - * description: The response payload for WADO-RS Thumbnail - * content: - * "image/jpeg": - * schema: - * type: string - * format: binary - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/thumbnail", - require("./controller/WADO-RS/thumbnail/series") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/thumbnail: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's Series' instances' Thumbnail - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * responses: - * 200: - * description: The response payload for WADO-RS Thumbnail - * content: - * "image/jpeg": - * schema: - * type: string - * format: binary - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/thumbnail", - require("./controller/WADO-RS/thumbnail/instance") -); - -/** - * @openapi - * /dicom-web/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}/frames/{frameNumbers}/thumbnail: - * get: - * tags: - * - WADO-RS - * description: Retrieve Study's instances' metadata - * parameters: - * - $ref: "#/components/parameters/studyUID" - * - $ref: "#/components/parameters/seriesUID" - * - $ref: "#/components/parameters/instanceUID" - * - $ref: "#/components/parameters/frameNumbers" - * responses: - * 200: - * description: The response payload for WADO-RS Thumbnail - * content: - * "image/jpeg": - * schema: - * type: string - * format: binary - * - */ -router.get( - "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/frames/:frameNumber/thumbnail", - validateParams({ - frameNumber : intArrayJoi.intArray().items(Joi.number().integer().min(1)).single() - } , "params" , {allowUnknown : true}), - require("./controller/WADO-RS/thumbnail/frame") -); - - - -//#endregion - -module.exports = router; \ No newline at end of file diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index 03546ccd..43fb8cc3 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -15,7 +15,8 @@ "@wado-rs-service": ["./api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"], - "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"] + "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"], + "@thumbnail-service": ["./api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js"] } }, "exclude": [ diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index 30bc0646..261a8e39 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -14,6 +14,7 @@ "@wado-rs-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js", - "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js" + "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js", + "@thumbnail-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js" } } \ No newline at end of file diff --git a/routes.js b/routes.js index cc3f9cfb..926f1329 100644 --- a/routes.js +++ b/routes.js @@ -22,10 +22,10 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/stow-rs.route")); app.use("/dicom-web", require("./api/dicom-web/qido-rs.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-instance.route")); - app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-metadata.route")); + app.use("/dicom-web", require("./api/dicom-web/wado-rs-metadata.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-rendered.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); - app.use("/dicom-web", require("./api-sql/dicom-web/wado-rs-thumbnail.route")); + app.use("/dicom-web", require("./api/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); // app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); From 029f84425c0f2e2e7a751409ff7500adddf36bdd Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 21:56:48 +0800 Subject: [PATCH 166/365] refactor: rename `convertAllQueryToDICOMTag` to `convertAllQueryToDicomTag` --- .../QIDO-RS/service/QIDO-RS.service.js | 4 ++-- .../QIDO-RS/service/QIDO-RS.service.js | 6 +++--- .../UPS-RS/service/get-workItem.service.js | 4 ++-- .../UPS-RS/service/subscribe.service.js | 4 ++-- .../UPS-RS/service/unsubscribe.service.js | 2 +- dimse-sql/queryBuilder.js | 3 ++- test/QIDO-RS-Service/common.test.js | 14 ++++++------- test/QIDO-RS-Service/patient.test.js | 10 +++++----- test/query.test.js | 20 +++++++++---------- 9 files changed, 34 insertions(+), 33 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 7f594750..d2d8f423 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -1,6 +1,6 @@ const _ = require("lodash"); -const { QidoRsService, convertAllQueryToDICOMTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { QidoRsService, convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); QidoRsService.prototype.initQuery_ = function () { let query = _.cloneDeep(this.request.query); @@ -10,7 +10,7 @@ QidoRsService.prototype.initQuery_ = function () { if (!query[queryKey]) delete query[queryKey]; } - this.query = convertAllQueryToDICOMTag(query, false); + this.query = convertAllQueryToDicomTag(query, false); }; module.exports.QidoRsService = QidoRsService; \ No newline at end of file diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 56d4acda..5bb06e46 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -69,7 +69,7 @@ class QidoRsService { if (!query[queryKey]) delete query[queryKey]; } - this.query = convertAllQueryToDICOMTag(query); + this.query = convertAllQueryToDicomTag(query); } async getAndResponseDicomJson() { @@ -139,7 +139,7 @@ class QidoRsService { * @param {Object} iParam The request query. * @returns */ -function convertAllQueryToDICOMTag(iParam, pushSuffixValue=true) { +function convertAllQueryToDicomTag(iParam, pushSuffixValue=true) { let keys = Object.keys(iParam); let newQS = {}; for (let i = 0; i < keys.length; i++) { @@ -180,4 +180,4 @@ function convertAllQueryToDICOMTag(iParam, pushSuffixValue=true) { //#endregion module.exports.QidoRsService = QidoRsService; -module.exports.convertAllQueryToDICOMTag = convertAllQueryToDICOMTag; +module.exports.convertAllQueryToDicomTag = convertAllQueryToDicomTag; diff --git a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 9b9f88d5..558a6b49 100644 --- a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -3,7 +3,7 @@ const workItemsModel = require("@models/mongodb/models/workItems"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); -const { convertAllQueryToDICOMTag } = require("../../QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); class GetWorkItemService { constructor(req, res) { @@ -60,7 +60,7 @@ class GetWorkItemService { if (!query[queryKey]) delete query[queryKey]; } - this.query = convertAllQueryToDICOMTag(query); + this.query = convertAllQueryToDicomTag(query); } } diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index a71eae9b..f50593f1 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -11,7 +11,7 @@ const { const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); const { BaseWorkItemService } = require("./base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); -const { convertAllQueryToDICOMTag } = require("../../QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); class SubscribeService extends BaseWorkItemService { @@ -34,7 +34,7 @@ class SubscribeService extends BaseWorkItemService { if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID || this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID) { - this.query = convertAllQueryToDICOMTag(this.request.query); + this.query = convertAllQueryToDicomTag(this.request.query); await this.createOrUpdateGlobalSubscription(); } else { let workItem = await this.findOneWorkItem(this.upsInstanceUID); diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 62fac381..51fa0e82 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -10,7 +10,7 @@ const { } = require("@error/dicom-web-service"); const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); const { BaseWorkItemService } = require("./base-workItem.service"); -const { convertAllQueryToDICOMTag } = require("../../QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); class UnSubscribeService extends BaseWorkItemService { diff --git a/dimse-sql/queryBuilder.js b/dimse-sql/queryBuilder.js index 65a4160e..779addc1 100644 --- a/dimse-sql/queryBuilder.js +++ b/dimse-sql/queryBuilder.js @@ -17,7 +17,8 @@ class SqlDimseQueryBuilder extends DimseQueryBuilder { async getSqlQuery(query) { return convertAllQueryToDicomTag( - this.cleanEmptyQuery(query) + this.cleanEmptyQuery(query), + false ); } } diff --git a/test/QIDO-RS-Service/common.test.js b/test/QIDO-RS-Service/common.test.js index 3f7e9888..82789ac5 100644 --- a/test/QIDO-RS-Service/common.test.js +++ b/test/QIDO-RS-Service/common.test.js @@ -4,7 +4,7 @@ const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); const { - convertAllQueryToDICOMTag + convertAllQueryToDicomTag } = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { convertRequestQueryToMongoQuery } = require("../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const moment = require("moment"); @@ -42,7 +42,7 @@ describe("QIDO-RS Service Common Function", () => { */ it("Should convert `00100010=foobar1234`, VR: `PN` to Mongo query", async ()=> { - let query = convertAllQueryToDICOMTag({ + let query = convertAllQueryToDicomTag({ "00100010": "foobar1234" }); @@ -82,7 +82,7 @@ describe("QIDO-RS Service Common Function", () => { describe("Convert `Date` VR: `DA` query", ()=> { it("Should convert `00100030=19991111` to Mongo query", async ()=> { - let query = convertAllQueryToDICOMTag({ + let query = convertAllQueryToDicomTag({ "00100030": "19991111" }); @@ -109,7 +109,7 @@ describe("QIDO-RS Service Common Function", () => { }); it("Should convert `00100030=19991111-` to Mongo query", async ()=> { - let query = convertAllQueryToDICOMTag({ + let query = convertAllQueryToDicomTag({ "00100030": "19991111-" }); @@ -135,7 +135,7 @@ describe("QIDO-RS Service Common Function", () => { }); it("Should convert `00100030=-19991111` to Mongo query", async ()=> { - let query = convertAllQueryToDICOMTag({ + let query = convertAllQueryToDicomTag({ "00100030": "-19991111" }); @@ -161,7 +161,7 @@ describe("QIDO-RS Service Common Function", () => { }); it("Should convert `00100030=19900101-19991111` to Mongo query", async ()=> { - let query = convertAllQueryToDICOMTag({ + let query = convertAllQueryToDicomTag({ "00100030": "19900101-19991111" }); @@ -191,7 +191,7 @@ describe("QIDO-RS Service Common Function", () => { describe("Convert string `00100020=foobar` VR: `LO`", () => { it("Should convert string completely", async ()=> { - let query = convertAllQueryToDICOMTag({ + let query = convertAllQueryToDicomTag({ "00100020": "foobar" }); diff --git a/test/QIDO-RS-Service/patient.test.js b/test/QIDO-RS-Service/patient.test.js index d2fc319e..54fb3740 100644 --- a/test/QIDO-RS-Service/patient.test.js +++ b/test/QIDO-RS-Service/patient.test.js @@ -3,7 +3,7 @@ const patientModel = require("../../models/mongodb/models/patient.model"); const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); -const { convertAllQueryToDICOMTag } = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { QueryPatientDicomJsonFactory } = require("../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); describe("Patient QIDO-RS Service", async () => { @@ -97,7 +97,7 @@ describe("Patient QIDO-RS Service", async () => { let q = { "00100020": "foobar123456" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { @@ -114,7 +114,7 @@ describe("Patient QIDO-RS Service", async () => { let q = { "00100020": "foobar123" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { @@ -134,7 +134,7 @@ describe("Patient QIDO-RS Service", async () => { let q = { "00100010": "John*" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { @@ -151,7 +151,7 @@ describe("Patient QIDO-RS Service", async () => { let q = { "00100010": "John Doe" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryPatientDicomJsonFactory({ query: { diff --git a/test/query.test.js b/test/query.test.js index 062a89fd..483bb96c 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -9,7 +9,7 @@ const dicomStudyModel = require("../models/mongodb/models/study.model"); const dicomSeriesModel = require("../models/mongodb/models/series.model"); const dicomModel = require("../models/mongodb/models/instance.model"); const { expect } = require("chai"); -const { convertAllQueryToDICOMTag } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); @@ -26,7 +26,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "StudyDate": "19990101-19991231" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -45,7 +45,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "StudyDate": "20220101-20221231" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -66,7 +66,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "PatientID": "TCGA-G4-6304" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -89,7 +89,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "StudyDate": "20100101-20101231" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -109,7 +109,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "StudyDate": "19990101-19991231" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -131,7 +131,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "PatientBirthDate": "19590101" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -151,7 +151,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "PatientBirthDate": "19601218" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -173,7 +173,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "AccessionNumber": "4444" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { @@ -193,7 +193,7 @@ describe("Query DICOM of study, series, and instance level", async () => { "AccessionNumber": "2794663908550664" }; - q = convertAllQueryToDICOMTag(q); + q = convertAllQueryToDicomTag(q); let dicomJsonFactory = new QueryStudyDicomJsonFactory({ query: { From 18073408c23c1e846926180e2c70632f2dcd5516 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 23:15:14 +0800 Subject: [PATCH 167/365] refactor: query tasks of hierarchy level (patient, study, series, and instance) - Added "@dimse-query-builder" to the config files. - Refactored the "getInstanceQueryTaskInjectProxy" method in the "JsInstanceQueryTask" class. - Refactored the "getPatientQueryTaskInjectProxy" method in the "SqlJsPatientQueryTask" class. --- config/jsconfig.sql.json | 3 +- config/modula-alias/sql/package.json | 3 +- dimse-sql/instanceQueryTask.js | 100 ++++----------- dimse-sql/patientQueryTask.js | 183 +++++++-------------------- dimse-sql/queryBuilder.js | 5 +- dimse-sql/seriesQueryTask.js | 97 ++++---------- dimse-sql/studyQueryTask.js | 112 ++++++---------- dimse-sql/utils.js | 14 +- dimse/instanceQueryTask.js | 4 +- dimse/patientQueryTask.js | 3 +- dimse/seriesQueryTask.js | 4 +- dimse/studyQueryTask.js | 4 +- dimse/utils.js | 3 +- 13 files changed, 162 insertions(+), 373 deletions(-) diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index 43fb8cc3..6f779d7d 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -16,7 +16,8 @@ "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"], "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"], - "@thumbnail-service": ["./api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js"] + "@thumbnail-service": ["./api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], + "@dimse-query-builder": ["./dimse-sql/queryBuilder.js"] } }, "exclude": [ diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index 261a8e39..a55732de 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -15,6 +15,7 @@ "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js", "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js", - "@thumbnail-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js" + "@thumbnail-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js", + "@dimse-query-builder": "../../../dimse-sql/queryBuilder.js" } } \ No newline at end of file diff --git a/dimse-sql/instanceQueryTask.js b/dimse-sql/instanceQueryTask.js index 29bef741..b48400e0 100644 --- a/dimse-sql/instanceQueryTask.js +++ b/dimse-sql/instanceQueryTask.js @@ -9,6 +9,8 @@ const { createInstanceQueryTaskInjectProxy } = require("@java-wrapper/org/github const { InstanceModel } = require("@models/sql/models/instance.model"); const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); const { Tag } = require("@dcm4che/data/Tag"); +const { InstanceQueryTaskInjectProxy, InstanceMatchIteratorProxy } = require("@root/dimse/instanceQueryTask"); +const { QueryTaskUtils } = require("@root/dimse/utils"); class JsInstanceQueryTask extends JsSeriesQueryTask { @@ -35,94 +37,34 @@ class JsInstanceQueryTask extends JsSeriesQueryTask { ); await super.get(); - await this.instanceQueryTaskInjectMethods.wrappedFindNextInstance(); + await this.instanceQueryTaskInjectProxy.wrappedFindNextInstance(); return instanceQueryTask; } getQueryTaskInjectProxy() { - this.instanceBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.instanceAttr); - }, - nextMatch: async () => { - let returnAttr = await Attributes.newInstanceAsync( - await this.patientAttr.size() + await this.studyAttr.size() + await this.seriesAttr.size() + await this.instanceAttr.size() - ); - await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr, this.seriesAttr, this.instanceAttr]); - await returnAttr.addAll(this.patientAttr); - await returnAttr.addAll(this.studyAttr, true); - await returnAttr.addAll(this.seriesAttr, true); - await returnAttr.addAll(this.instanceAttr, true); - - await this.instanceQueryTaskInjectMethods.wrappedFindNextInstance(); - - return returnAttr; - }, - adjust: async (match) => { - return await this.patientAdjust(match); - } - }; - - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.instanceBasicQueryTaskInjectMethods); + if (!this.matchIteratorProxy) { + this.matchIteratorProxy = new InstanceMatchIteratorProxy(this); } - - return this.queryTaskInjectProxy; + return this.matchIteratorProxy.get(); } getInstanceQueryTaskInjectProxy() { - /** @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTaskInject").InstanceQueryTaskInjectInterface } */ - this.instanceQueryTaskInjectMethods = { - wrappedFindNextInstance: async () => { - await this.instanceQueryTaskInjectMethods.findNextInstance(); - }, - getInstance: async () => { - await this.getNextInstance(); - }, - findNextInstance: async () => { - if (!this.seriesAttr) - return false; - - if (!this.instanceAttr) { - await this.getNextInstanceCursor(); - await this.instanceQueryTaskInjectMethods.getInstance(); - } else { - await this.instanceQueryTaskInjectMethods.getInstance(); - } - - while (!this.instanceAttr && await this.seriesQueryTaskInjectMethods.findNextSeries()) { - await this.getNextInstanceCursor(); - await this.instanceQueryTaskInjectMethods.getInstance(); - } - - return _.isNull(this.instanceAttr); - } - }; - if (!this.instanceQueryTaskInjectProxy) { - this.instanceQueryTaskInjectProxy = createInstanceQueryTaskInjectProxy(this.instanceQueryTaskInjectMethods); + this.instanceQueryTaskInjectProxy = new SqlInstanceQueryTaskInjectProxy(this); } - return this.instanceQueryTaskInjectProxy; + return this.instanceQueryTaskInjectProxy.get(); } async getNextInstanceCursor() { this.instanceOffset = 0; - let queryAttr = await Attributes.newInstanceAsync(); - await Attributes.unifyCharacterSets([this.keys, this.patientAttr, this.studyAttr, this.seriesAttr]); - await queryAttr.addAll(this.keys, true); - await queryAttr.addSelected(this.seriesAttr, [Tag.PatientID]); - await queryAttr.addSelected(this.seriesAttr, [Tag.StudyInstanceUID]); - await queryAttr.addSelected(this.seriesAttr, [Tag.SeriesInstanceUID]); - - let queryBuilder = new DimseQueryBuilder(queryAttr, "instance"); - let normalQuery = await queryBuilder.toNormalQuery(); - let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.seriesAttr); + let dbQuery = await QueryTaskUtils.getDbQuery(queryAttr, "instance"); let instanceQueryBuilder = new InstanceQueryBuilder({ query: { - ...sqlQuery + ...dbQuery } }); let q = instanceQueryBuilder.build(); @@ -131,18 +73,22 @@ class JsInstanceQueryTask extends JsSeriesQueryTask { }; } - async getNextInstance() { - let instance = await InstanceModel.findOne({ - ...this.instanceQuery, +} + +class SqlInstanceQueryTaskInjectProxy extends InstanceQueryTaskInjectProxy { + constructor(instanceQueryTask) { + super(instanceQueryTask); + } + + async getInstance() { + this.instanceQueryTask.instance = await InstanceModel.findOne({ + ...this.instanceQueryTask.instanceQuery, attributes: ["json"], limit: 1, - offset: this.instanceOffset++ + offset: this.instanceQueryTask.instanceOffset++ }); - this.instance = instance; - this.instanceAttr = this.instance ? await this.instance.getAttributes() : null; + this.instanceQueryTask.instanceAttr = this.instanceQueryTask.instance ? await this.instanceQueryTask.instance.getAttributes() : null; } - } - module.exports.JsInstanceQueryTask = JsInstanceQueryTask; \ No newline at end of file diff --git a/dimse-sql/patientQueryTask.js b/dimse-sql/patientQueryTask.js index b6419a2b..42e4145b 100644 --- a/dimse-sql/patientQueryTask.js +++ b/dimse-sql/patientQueryTask.js @@ -1,150 +1,30 @@ const _ = require("lodash"); -const { default: PatientQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTask"); -const { PatientQueryTaskInjectInterface, createPatientQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTaskInject"); -const { createQueryTaskInjectProxy, QueryTaskInjectInterface } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); -const { default: Attributes } = require("@dcm4che/data/Attributes"); -const { default: Tag } = require("@dcm4che/data/Tag"); -const { default: VR } = require("@dcm4che/data/VR"); -const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); -const { Association } = require("@dcm4che/net/Association"); -const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); +const { createPatientQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTaskInject"); +const { DimseQueryBuilder } = require("@dimse-query-builder"); const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); const { PatientModel } = require("@models/sql/models/patient.model"); +const { JsPatientQueryTask } = require("../dimse/patientQueryTask"); +const { QueryTaskUtils } = require("@root/dimse/utils"); -class JsPatientQueryTask { +class SqlJsPatientQueryTask extends JsPatientQueryTask { constructor(as, pc, rq, keys) { - /** @type { Association } */ - this.as = as; - /** @type { PresentationContext } */ - this.pc = pc; - /** @type { Attributes } */ - this.rq = rq; - /** @type { Attributes } */ - this.keys = keys; + super(as, pc, rq, keys); - this.patientAttr = null; this.offset = 0; this.query = null; - this.patient = null; - } - - async get() { - let patientQueryTask = await PatientQueryTask.newInstanceAsync( - this.as, - this.pc, - this.rq, - this.keys, - this.getQueryTaskInjectProxy(), - this.getPatientQueryTaskInjectProxy() - ); - - await this.initQuery(); - await this.patientQueryTaskInjectMethods.wrappedFindNextPatient(); - - return patientQueryTask; - } - - getQueryTaskInjectProxy() { - /** @type { QueryTaskInjectInterface } */ - this.patientBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.patientAttr); - }, - nextMatch: async () => { - let tempRecord = this.patientAttr; - await this.patientQueryTaskInjectMethods.wrappedFindNextPatient(); - return tempRecord; - }, - adjust: async (match) => { - return this.patientAdjust(match); - } - }; - - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.patientBasicQueryTaskInjectMethods); - } - - return this.queryTaskInjectProxy; } getPatientQueryTaskInjectProxy() { - - /** @type { PatientQueryTaskInjectInterface }*/ - this.patientQueryTaskInjectMethods = { - wrappedFindNextPatient: async () => { - await this.patientQueryTaskInjectMethods.findNextPatient(); - }, - getPatient: async () => { - await this.nextPatient(); - }, - findNextPatient: async () => { - await this.patientQueryTaskInjectMethods.getPatient(); - return !_.isNull(this.patientAttr); - } - }; - if (!this.patientQueryTaskProxy) { - this.patientQueryTaskProxy = createPatientQueryTaskInjectProxy(this.patientQueryTaskInjectMethods); - } - - return this.patientQueryTaskProxy; - } - - /** - * - * @param {Attributes} match - * @returns - */ - async basicAdjust(match) { - if (match == null) { - return null; - } - - let filtered = new Attributes(await match.size()); - - if (!await this.keys.contains(Tag.SpecificCharacterSet)) { - let ss = await match.getStrings(Tag.SpecificCharacterSet); - if (!ss) - await filtered.setString(Tag.SpecificCharacterSet, VR.CS, ss); - } - await filtered.addSelected(match, this.keys); - await filtered.supplementEmpty(this.keys); - return filtered; - } - - async patientAdjust(match) { - let basicAd = await this.basicAdjust(match); - await basicAd.remove(Tag.DirectoryRecordType); - - if (await this.keys.contains(Tag.SOPClassUID)) { - await basicAd.setString(Tag.SOPClassUID, VR.UI, await match.getString(Tag.ReferencedSOPClassUIDInFile)); - } - - if (await this.keys.contains(Tag.SOPInstanceUID)) { - await basicAd.setString(Tag.SOPInstanceUID, VR.UI, await match.getString(Tag.ReferencedSOPInstanceUIDInFile)); - } - - await basicAd.setString(Tag.QueryRetrieveLevel, VR.CS, await this.keys.getString(Tag.QueryRetrieveLevel)); - - return basicAd; - } - - getReturnKeys(query) { - let returnKeys = {}; - let queryKeys = Object.keys(query); - for (let i = 0; i < queryKeys.length; i++) { - returnKeys[queryKeys[i].split(".").shift()] = 1; + this.patientQueryTaskProxy = new SqlPatientQueryTaskInjectProxy(this); } - return returnKeys; + return this.patientQueryTaskProxy.get(); } - async initQuery() { + async initCursor() { this.offset = 0; - let queryBuilder = new DimseQueryBuilder(this.keys, "patient"); - let normalQuery = await queryBuilder.toNormalQuery(); - let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); - + let sqlQuery = await QueryTaskUtils.getDbQuery(this.keys, "patient"); let patientQueryBuilder = new PatientQueryBuilder({ query: { ...sqlQuery @@ -156,17 +36,48 @@ class JsPatientQueryTask { }; } - async nextPatient() { +} + +class SqlPatientQueryTaskInjectProxy { + constructor(patientQueryTask) { + /** @type {SqlJsPatientQueryTask} */ + this.patientQueryTask = patientQueryTask; + } + + get() { + return createPatientQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + wrappedFindNextPatient: this.wrappedFindNextPatient.bind(this), + getPatient: this.getPatient.bind(this), + findNextPatient: this.findNextPatient.bind(this) + }; + } + + async wrappedFindNextPatient() { + await this.findNextPatient(); + } + + async findNextPatient() { + await this.getPatient(); + return !_.isNull(this.patientQueryTask.patientAttr); + } + + async getPatient() { let patient = await PatientModel.findOne({ - ...this.query, + ...this.patientQueryTask.query, attributes: ["json"], limit: 1, - offset: this.offset++ + offset: this.patientQueryTask.offset++ }); - this.patient = patient; - this.patientAttr = this.patient ? await this.patient.getAttributes() : null; + this.patientQueryTask.patient = patient; + this.patientQueryTask.patientAttr = this.patientQueryTask.patient ? await this.patientQueryTask.patient.getAttributes() : null; } } -module.exports.JsPatientQueryTask = JsPatientQueryTask; \ No newline at end of file +module.exports.JsPatientQueryTask = SqlJsPatientQueryTask; \ No newline at end of file diff --git a/dimse-sql/queryBuilder.js b/dimse-sql/queryBuilder.js index 779addc1..3227ebe4 100644 --- a/dimse-sql/queryBuilder.js +++ b/dimse-sql/queryBuilder.js @@ -15,7 +15,7 @@ class SqlDimseQueryBuilder extends DimseQueryBuilder { super(queryKeys, level); } - async getSqlQuery(query) { + async build(query) { return convertAllQueryToDicomTag( this.cleanEmptyQuery(query), false @@ -23,4 +23,5 @@ class SqlDimseQueryBuilder extends DimseQueryBuilder { } } -module.exports.SqlDimseQueryBuilder = SqlDimseQueryBuilder; \ No newline at end of file +module.exports.SqlDimseQueryBuilder = SqlDimseQueryBuilder; +module.exports.DimseQueryBuilder = SqlDimseQueryBuilder; \ No newline at end of file diff --git a/dimse-sql/seriesQueryTask.js b/dimse-sql/seriesQueryTask.js index a7b328f8..bcdcf38d 100644 --- a/dimse-sql/seriesQueryTask.js +++ b/dimse-sql/seriesQueryTask.js @@ -1,14 +1,16 @@ const _ = require("lodash"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); -const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); +const { DimseQueryBuilder } = require("@dimse-query-builder"); const { JsStudyQueryTask } = require("./studyQueryTask"); const { SeriesQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTask"); const { Attributes } = require("@dcm4che/data/Attributes"); -const { createSeriesQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTaskInject"); const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder"); const { SeriesModel } = require("@models/sql/models/series.model"); const { Tag } = require("@dcm4che/data/Tag"); +const { SeriesQueryTaskInjectProxy, SeriesMatchIteratorProxy } = require("@root/dimse/seriesQueryTask"); +const { QueryTaskUtils } = require("@root/dimse/utils"); + class JsSeriesQueryTask extends JsStudyQueryTask { constructor(as, pc, rq, keys) { @@ -33,89 +35,31 @@ class JsSeriesQueryTask extends JsStudyQueryTask { ); await super.get(); - await this.seriesQueryTaskInjectMethods.wrappedFindNextSeries(); + await this.seriesQueryTaskInjectProxy.wrappedFindNextSeries(); return seriesQueryTask; } getQueryTaskInjectProxy() { - /** @type { QueryTaskInjectInterface } */ - this.seriesBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.seriesAttr); - }, - nextMatch: async () => { - let returnAttr = await Attributes.newInstanceAsync( - await this.patientAttr.size() + await this.studyAttr.size() + await this.seriesAttr.size() - ); - await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr, this.seriesAttr]); - await returnAttr.addAll(this.patientAttr); - await returnAttr.addAll(this.studyAttr, true); - await returnAttr.addAll(this.seriesAttr, true); - - await this.seriesQueryTaskInjectMethods.wrappedFindNextSeries(); - - return returnAttr; - }, - adjust: async (match) => { - return await this.patientAdjust(match); - } - }; - - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.seriesBasicQueryTaskInjectMethods); + if (!this.matchIteratorProxy) { + this.matchIteratorProxy = new SeriesMatchIteratorProxy(this); } - return this.queryTaskInjectProxy; + return this.matchIteratorProxy.get(); } getSeriesQueryTaskInjectProxy() { - /** @type {import("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTaskInject").SeriesQueryTaskInjectInterface} */ - this.seriesQueryTaskInjectMethods = { - wrappedFindNextSeries: async () => { - await this.seriesQueryTaskInjectMethods.findNextSeries(); - }, - getSeries: async () => { - await this.getNextSeries(); - }, - findNextSeries: async () => { - if (!this.studyAttr) - return false; - - if (!this.seriesAttr) { - await this.getNextSeriesCursor(); - await this.seriesQueryTaskInjectMethods.getSeries(); - } else { - await this.seriesQueryTaskInjectMethods.getSeries(); - } - - while (!this.seriesAttr && await this.studyQueryTaskInjectMethods.findNextStudy()) { - await this.getNextSeriesCursor(); - await this.seriesQueryTaskInjectMethods.getSeries(); - } - - return !_.isNull(this.seriesAttr); - } - }; - if (!this.seriesQueryTaskInjectProxy) { - this.seriesQueryTaskInjectProxy = createSeriesQueryTaskInjectProxy(this.seriesQueryTaskInjectMethods); + this.seriesQueryTaskInjectProxy = new SqlSeriesQueryTaskInjectProxy(this); } - return this.seriesQueryTaskInjectProxy; + return this.seriesQueryTaskInjectProxy.get(); } async getNextSeriesCursor() { this.seriesOffset = 0; - let queryAttr = await Attributes.newInstanceAsync(); - await Attributes.unifyCharacterSets([this.keys, this.patientAttr, this.studyAttr]); - await queryAttr.addAll(this.keys, true); - await queryAttr.addSelected(this.studyAttr, [Tag.PatientID]); - await queryAttr.addSelected(this.studyAttr, [Tag.StudyInstanceUID]); - - let queryBuilder = new DimseQueryBuilder(queryAttr, "series"); - let normalQuery = await queryBuilder.toNormalQuery(); - let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.studyAttr, "series"); + let sqlQuery = await QueryTaskUtils.getDbQuery(queryAttr, "series"); let seriesQueryBuilder = new SeriesQueryBuilder({ query: { @@ -128,16 +72,21 @@ class JsSeriesQueryTask extends JsStudyQueryTask { }; } - async getNextSeries() { - let series = await SeriesModel.findOne({ - ...this.seriesQuery, +} + +class SqlSeriesQueryTaskInjectProxy extends SeriesQueryTaskInjectProxy { + constructor(seriesQueryTask) { + super(seriesQueryTask); + } + async getSeries() { + this.seriesQueryTask.series = await SeriesModel.findOne({ + ...this.seriesQueryTask.seriesQuery, attributes: ["json"], limit: 1, - offset: this.seriesOffset++ + offset: this.seriesQueryTask.seriesOffset++ }); - this.series = series; - this.seriesAttr = this.series ? await this.series.getAttributes() : null; + this.seriesQueryTask.seriesAttr = this.seriesQueryTask.series ? await this.seriesQueryTask.series.getAttributes() : null; } } diff --git a/dimse-sql/studyQueryTask.js b/dimse-sql/studyQueryTask.js index ca85e314..79854318 100644 --- a/dimse-sql/studyQueryTask.js +++ b/dimse-sql/studyQueryTask.js @@ -2,19 +2,17 @@ const _ = require("lodash"); const { StudyQueryTask } = require("@chinlinlee/dcm777/net/StudyQueryTask"); const { JsPatientQueryTask } = require("./patientQueryTask"); -const { default: Tag } = require("@dcm4che/data/Tag"); -const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); -const { StudyQueryTaskInjectInterface, createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); -const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); +const { createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); const { Attributes } = require("@dcm4che/data/Attributes"); const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder"); const { StudyModel } = require("@models/sql/models/study.model"); +const { StudyQueryTaskInjectProxy, StudyMatchIteratorProxy } = require("@root/dimse/studyQueryTask"); +const { QueryTaskUtils } = require("@root/dimse/utils"); class JsStudyQueryTask extends JsPatientQueryTask { constructor(as, pc, rq, keys) { super(as, pc, rq, keys); - this.studyCursor = false; this.study = null; /** @type { Attributes | null } */ this.studyAttr = null; @@ -32,87 +30,31 @@ class JsStudyQueryTask extends JsPatientQueryTask { ); await super.get(); - await this.studyQueryTaskInjectMethods.wrappedFindNextStudy(); + await this.studyQueryTaskInjectProxy.wrappedFindNextStudy(); return studyQueryTask; } getQueryTaskInjectProxy() { - /** @type { QueryTaskInjectInterface } */ - this.studyBasicQueryTaskInjectMethods = { - hasMoreMatches: () => { - return !_.isNull(this.studyAttr); - }, - nextMatch: async () => { - let returnAttr = await Attributes.newInstanceAsync( - await this.patientAttr.size() + await this.studyAttr.size() - ); - await Attributes.unifyCharacterSets([this.patientAttr, this.studyAttr]); - await returnAttr.addAll(this.patientAttr); - await returnAttr.addAll(this.studyAttr); - - await this.studyQueryTaskInjectMethods.wrappedFindNextStudy(); - - return returnAttr; - }, - adjust: async (match) => { - return await this.patientAdjust(match); - } - }; - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.studyBasicQueryTaskInjectMethods); + this.queryTaskInjectProxy = new StudyMatchIteratorProxy(this); } - return this.queryTaskInjectProxy; + return this.queryTaskInjectProxy.get(); } getStudyQueryTaskInjectProxy() { - /** @type { StudyQueryTaskInjectInterface } */ - this.studyQueryTaskInjectMethods = { - wrappedFindNextStudy: async () => { - await this.studyQueryTaskInjectMethods.findNextStudy(); - }, - getStudy: async () => { - await this.getNextStudy(); - }, - findNextStudy: async () => { - if (!this.patientAttr) - return false; - - if (!this.studyAttr) { - await this.getNextStudyCursor(); - await this.studyQueryTaskInjectMethods.getStudy(); - } else { - await this.studyQueryTaskInjectMethods.getStudy(); - } - - while (!this.studyAttr && await this.patientQueryTaskInjectMethods.findNextPatient()) { - await this.getNextStudyCursor(); - await this.studyQueryTaskInjectMethods.getStudy(); - } - - return !_.isNull(this.studyAttr); - } - }; - if (!this.studyQueryTaskInjectProxy) { - this.studyQueryTaskInjectProxy = createStudyQueryTaskInjectProxy(this.studyQueryTaskInjectMethods); + this.studyQueryTaskInjectProxy = new SqlStudyQueryTaskInjectProxy(this); } - return this.studyQueryTaskInjectProxy; + return this.studyQueryTaskInjectProxy.get(); } async getNextStudyCursor() { this.studyOffset = 0; - let queryAttr = await Attributes.newInstanceAsync(); - await Attributes.unifyCharacterSets([this.keys, this.patientAttr]); - await queryAttr.addAll(this.keys, true); - await queryAttr.addSelected(this.patientAttr, [Tag.PatientID]); - - let queryBuilder = new DimseQueryBuilder(queryAttr, "study"); - let normalQuery = await queryBuilder.toNormalQuery(); - let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.patientAttr, "study"); + let sqlQuery = await QueryTaskUtils.getDbQuery(queryAttr, "study"); let studyQueryBuilder = new StudyQueryBuilder({ query: { @@ -125,17 +67,39 @@ class JsStudyQueryTask extends JsPatientQueryTask { }; } - async getNextStudy() { - let study = await StudyModel.findOne({ - ...this.studyQuery, + async auditDicomInstancesAccessed() { + if (!this.study) + return; + + let auditManager = await QueryTaskUtils.getAuditManager(this.as); + let studyUID = _.get(this.study, "x0020000D"); + auditManager.onDicomInstancesAccessed([studyUID]); + } +} + +class SqlStudyQueryTaskInjectProxy extends StudyQueryTaskInjectProxy { + constructor(studyQueryTask) { + super(studyQueryTask); + } + + get() { + return createStudyQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + async getStudy() { + this.studyQueryTask.study = await StudyModel.findOne({ + ...this.studyQueryTask.studyQuery, attributes: ["json"], limit: 1, - offset: this.studyOffset++ + offset: this.studyQueryTask.studyOffset++ }); - this.study = study; - this.studyAttr = this.study ? await this.study.getAttributes() : null; + this.studyQueryTask.auditDicomInstancesAccessed(); + this.studyQueryTask.studyAttr = this.studyQueryTask.study ? await this.studyQueryTask.study.getAttributes() : null; } + } module.exports.JsStudyQueryTask = JsStudyQueryTask; \ No newline at end of file diff --git a/dimse-sql/utils.js b/dimse-sql/utils.js index e5f35789..643932f2 100644 --- a/dimse-sql/utils.js +++ b/dimse-sql/utils.js @@ -1,13 +1,13 @@ const _ = require("lodash"); const path = require("path"); const { Attributes } = require("@dcm4che/data/Attributes"); -const mongoose = require("mongoose"); const { importClass } = require("java-bridge"); const { raccoonConfig } = require("@root/config-class"); const { InstanceLocator } = require("@dcm4che/net/service/InstanceLocator"); const { default: File } = require("@java-wrapper/java/io/File"); const sequenceInstance = require("@models/sql/instance"); const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); +const { QueryTaskUtils } = require("@root/dimse/utils"); /** * * @param {number} tag @@ -25,7 +25,7 @@ async function getInstancesFromKeysAttr(keys) { const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); let queryBuilder = new DimseQueryBuilder(keys, "instance"); let normalQuery = await queryBuilder.toNormalQuery(); - let sqlQuery = await queryBuilder.getSqlQuery(normalQuery); + let sqlQuery = await queryBuilder.build(normalQuery); let instanceQueryBuilder = new InstanceQueryBuilder({ query: { ...sqlQuery @@ -54,7 +54,7 @@ async function getInstancesFromKeysAttr(keys) { let fileUri = await instanceFile.toURI(); let fileUriString = await fileUri.toString(); - + let instanceLocator = await InstanceLocator.newInstanceAsync( _.get(instance.json, "00080016.Value.0"), _.get(instance.json, "00080018.Value.0"), @@ -96,6 +96,14 @@ async function findOneInstanceFromKeysAttr(keys) { return instance.json; } +QueryTaskUtils.getDbQuery = async function (queryAttr, level = "patient") { + let queryBuilder = await QueryTaskUtils.getQueryBuilder(queryAttr, level); + let normalQuery = await queryBuilder.toNormalQuery(); + let dbQuery = await queryBuilder.build(normalQuery); + + return dbQuery; +}; + module.exports.intTagToString = intTagToString; module.exports.getInstancesFromKeysAttr = getInstancesFromKeysAttr; module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr; \ No newline at end of file diff --git a/dimse/instanceQueryTask.js b/dimse/instanceQueryTask.js index 393147de..39e61cd9 100644 --- a/dimse/instanceQueryTask.js +++ b/dimse/instanceQueryTask.js @@ -177,4 +177,6 @@ class InstanceMatchIteratorProxy { } } -module.exports.JsInstanceQueryTask = JsInstanceQueryTask; \ No newline at end of file +module.exports.JsInstanceQueryTask = JsInstanceQueryTask; +module.exports.InstanceQueryTaskInjectProxy = InstanceQueryTaskInjectProxy; +module.exports.InstanceMatchIteratorProxy = InstanceMatchIteratorProxy; \ No newline at end of file diff --git a/dimse/patientQueryTask.js b/dimse/patientQueryTask.js index b72705f6..f1c74373 100644 --- a/dimse/patientQueryTask.js +++ b/dimse/patientQueryTask.js @@ -191,4 +191,5 @@ class PatientMatchIteratorProxy { } } -module.exports.JsPatientQueryTask = JsPatientQueryTask; \ No newline at end of file +module.exports.JsPatientQueryTask = JsPatientQueryTask; +module.exports.PatientMatchIteratorProxy = PatientMatchIteratorProxy; \ No newline at end of file diff --git a/dimse/seriesQueryTask.js b/dimse/seriesQueryTask.js index 47deb050..7d20e19e 100644 --- a/dimse/seriesQueryTask.js +++ b/dimse/seriesQueryTask.js @@ -171,4 +171,6 @@ class SeriesMatchIteratorProxy { } } -module.exports.JsSeriesQueryTask = JsSeriesQueryTask; \ No newline at end of file +module.exports.JsSeriesQueryTask = JsSeriesQueryTask; +module.exports.SeriesQueryTaskInjectProxy = SeriesQueryTaskInjectProxy; +module.exports.SeriesMatchIteratorProxy = SeriesMatchIteratorProxy; \ No newline at end of file diff --git a/dimse/studyQueryTask.js b/dimse/studyQueryTask.js index cd300a20..c94960ab 100644 --- a/dimse/studyQueryTask.js +++ b/dimse/studyQueryTask.js @@ -178,4 +178,6 @@ class StudyMatchIteratorProxy { } } -module.exports.JsStudyQueryTask = JsStudyQueryTask; \ No newline at end of file +module.exports.JsStudyQueryTask = JsStudyQueryTask; +module.exports.StudyMatchIteratorProxy = StudyMatchIteratorProxy; +module.exports.StudyQueryTaskInjectProxy = StudyQueryTaskInjectProxy; \ No newline at end of file diff --git a/dimse/utils.js b/dimse/utils.js index 33e2c2ec..595af81d 100644 --- a/dimse/utils.js +++ b/dimse/utils.js @@ -106,13 +106,14 @@ class QueryTaskUtils { static async getQueryAttribute(keys, parentAttr, level = "patient") { let queryAttr = await Attributes.newInstanceAsync(); + await Attributes.unifyCharacterSets([keys, parentAttr]); await queryAttr.addAll(keys); await queryAttr.addSelected(parentAttr, QUERY_ATTR_SELECTED_TAGS[level]); return queryAttr; } static async getQueryBuilder(queryAttr, level = "patient") { - const { DimseQueryBuilder } = require("./queryBuilder"); + const { DimseQueryBuilder } = require("@dimse-query-builder"); return new DimseQueryBuilder(queryAttr, level); } From 3c9fa12811d90f21290b2fd0916dcccc27cea23d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 18 Nov 2023 23:49:14 +0800 Subject: [PATCH 168/365] refactor: use module alias for dimse store, query and retrieve tasks - Added new query and retrieve tasks for DIMSE queries. - Updated the `jsconfig.sql.json` and `package.json` files to include the new tasks. - Removed the `c-find.js` and `c-get.js` files as they are no longer needed. --- config/jsconfig.sql.json | 8 +- config/modula-alias/sql/package.json | 8 +- dimse-sql/c-find.js | 67 --------- dimse-sql/c-get.js | 141 ------------------- dimse-sql/c-move.js | 194 --------------------------- dimse-sql/c-store.js | 49 ------- dimse-sql/index.js | 8 +- dimse-sql/instanceQueryTask.js | 4 - dimse-sql/level.js | 24 ---- dimse-sql/queryTagsOfEachLevel.js | 33 ----- dimse-sql/stgcmt.js | 149 -------------------- dimse-sql/utils.js | 3 +- dimse/c-find.js | 8 +- dimse/c-get.js | 2 +- dimse/c-move.js | 2 +- dimse/c-store.js | 2 +- 16 files changed, 27 insertions(+), 675 deletions(-) delete mode 100644 dimse-sql/c-find.js delete mode 100644 dimse-sql/c-get.js delete mode 100644 dimse-sql/c-move.js delete mode 100644 dimse-sql/c-store.js delete mode 100644 dimse-sql/level.js delete mode 100644 dimse-sql/queryTagsOfEachLevel.js delete mode 100644 dimse-sql/stgcmt.js diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index 6f779d7d..a8d0a396 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -11,13 +11,19 @@ "@dbModels/*": ["./models/sql/models/*"], "@dicom-json-model": ["./models/sql/dicom-json-model.js"], "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"], + "@stow-rs-service": ["./api/dicom-web/controller/STOW-RS/service/stow-rs.service.js"], "@qido-rs-service": ["./api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], "@wado-rs-service": ["./api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"], "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"], "@thumbnail-service": ["./api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], - "@dimse-query-builder": ["./dimse-sql/queryBuilder.js"] + "@dimse-query-builder": ["./dimse-sql/queryBuilder.js"], + "@dimse-patient-query-task": ["./dimse-sql/patientQueryTask.js"], + "@dimse-study-query-task": ["./dimse-sql/studyQueryTask.js"], + "@dimse-series-query-task": ["./dimse-sql/seriesQueryTask.js"], + "@dimse-instance-query-task": ["./dimse-sql/instanceQueryTask.js"], + "@dimse-utils": ["./dimse-sql/utils.js"] } }, "exclude": [ diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index a55732de..52a9b3cb 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -10,12 +10,18 @@ "@dbInitializer": "../../../models/sql/initializer.js", "@dicom-json-model": "../../../models/sql/dicom-json-model.js", "@query-dicom-json-factory": "../../../api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js", + "@stow-rs-service": "../../../api/dicom-web/controller/STOW-RS/service/stow-rs.service.js", "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", "@wado-rs-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js", "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js", "@thumbnail-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js", - "@dimse-query-builder": "../../../dimse-sql/queryBuilder.js" + "@dimse-query-builder": "../../../dimse-sql/queryBuilder.js", + "@dimse-patient-query-task": "../../../dimse-sql/patientQueryTask.js", + "@dimse-study-query-task": "../../../dimse-sql/studyQueryTask.js", + "@dimse-series-query-task": "../../../dimse-sql/seriesQueryTask.js", + "@dimse-instance-query-task": "../../../dimse-sql/instanceQueryTask.js", + "@dimse-utils": "../../../dimse-sql/utils.js" } } \ No newline at end of file diff --git a/dimse-sql/c-find.js b/dimse-sql/c-find.js deleted file mode 100644 index 51735bbe..00000000 --- a/dimse-sql/c-find.js +++ /dev/null @@ -1,67 +0,0 @@ -const { default: Attributes } = require("@dcm4che/data/Attributes"); -const { default: UID } = require("@dcm4che/data/UID"); -const { default: Association } = require("@dcm4che/net/Association"); -const { default: Dimse } = require("@dcm4che/net/Dimse"); -const { default: Status } = require("@dcm4che/net/Status"); -const { default: PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); -const { default: QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); -const { default: EnumSet } = require("@java-wrapper/java/util/EnumSet"); -const { default: BasicModCFindSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP"); -const { createCFindSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject"); -const { JsPatientQueryTask } = require("./patientQueryTask"); -const { JsStudyQueryTask } = require("./studyQueryTask"); -const { JsSeriesQueryTask } = require("./seriesQueryTask"); -const { JsInstanceQueryTask } = require("./instanceQueryTask"); -const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); -const { JsCFindScp } = require("@root/dimse/c-find"); - -class SqlJsCFindScp extends JsCFindScp{ - constructor() { - super(); - } - - getCFindScpInjectProxyMethods() { - /** - * @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject").CFindSCPInjectInterface } - */ - const cFindScpInjectProxyMethods = { - /** - * - * @param {Association} as - * @param {PresentationContext} pc - * @param {Dimse} dimse - * @param {Attributes} rq - * @param {Attributes} keys - */ - onDimseRQ: async (as, pc, dimse, rq, keys) => { - if (await dimse.compareTo(Dimse.C_FIND_RQ) !== 0) { - throw new Error(Status.UnrecognizedOperation); - } - let queryTask = await cFindScpInjectProxyMethods.calculateMatches(as, pc, rq, keys); - let applicationEntity = await as.getApplicationEntity(); - let device = await applicationEntity.getDevice(); - await device.execute(queryTask); - }, - calculateMatches: async (as, pc, rq, keys) => { - try { - let level = await this.scpObj.getQrLevel(as, pc, rq, keys); - if (await level.compareTo(QueryRetrieveLevel2.PATIENT) === 0) { - return await (new JsPatientQueryTask(as, pc, rq, keys)).get(); - } else if (await level.compareTo(QueryRetrieveLevel2.STUDY) === 0) { - return await (new JsStudyQueryTask(as, pc, rq, keys)).get(); - } else if (await level.compareTo(QueryRetrieveLevel2.SERIES) === 0) { - return await (new JsSeriesQueryTask(as, pc, rq, keys)).get(); - } else if (await level.compareTo(QueryRetrieveLevel2.IMAGE) === 0) { - return await (new JsInstanceQueryTask(as, pc, rq, keys)).get(); - } - } catch (e) { - console.error(e); - } - } - }; - - return cFindScpInjectProxyMethods; - } -} - -module.exports.SqlJsCFindScp = SqlJsCFindScp; \ No newline at end of file diff --git a/dimse-sql/c-get.js b/dimse-sql/c-get.js deleted file mode 100644 index 239d28e2..00000000 --- a/dimse-sql/c-get.js +++ /dev/null @@ -1,141 +0,0 @@ -const { UID } = require("@dcm4che/data/UID"); -const { createCGetSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CGetSCPInject"); -const { default: SimpleCGetSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCGetSCP"); -const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); -const { getInstancesFromKeysAttr } = require("./utils"); -const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); -const { Dimse } = require("@dcm4che/net/Dimse"); -const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); -const { default: QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); -const { DimseRetrieveAuditService } = require("@root/dimse/service/retrieveAudit.service"); -const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); - -class JsCGetScp { - constructor() { } - - getPatientRootLevel() { - const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { - keepAsDaemon: true - }); - - let simpleCGetScp = new SimpleCGetSCP( - cGetScpInject, - UID.PatientRootQueryRetrieveInformationModelGet, - PATIENT_ROOT_LEVELS - ); - - this.scpObj = simpleCGetScp; - - return simpleCGetScp; - } - - getStudyRootLevel() { - const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { - keepAsDaemon: true - }); - - let simpleCGetScp = new SimpleCGetSCP( - cGetScpInject, - UID.StudyRootQueryRetrieveInformationModelGet, - STUDY_ROOT_LEVELS - ); - - this.scpObj = simpleCGetScp; - - return simpleCGetScp; - } - - getPatientStudyOnlyLevel() { - const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { - keepAsDaemon: true - }); - - let simpleCGetScp = new SimpleCGetSCP( - cGetScpInject, - UID.PatientStudyOnlyQueryRetrieveInformationModelGet, - PATIENT_STUDY_ONLY_LEVELS - ); - - this.scpObj = simpleCGetScp; - - return simpleCGetScp; - } - - getCompositeLevel() { - const cGetScpInject = createCGetSCPInjectProxy(this.getCGetScpInjectProxyMethods(), { - keepAsDaemon: true - }); - - let simpleCGetScp = new SimpleCGetSCP( - cGetScpInject, - UID.CompositeInstanceRetrieveWithoutBulkDataGet, - EnumSet.ofSync(QueryRetrieveLevel2.IMAGE) - ); - - this.scpObj = simpleCGetScp; - - return simpleCGetScp; - } - - getCGetScpInjectProxyMethods() { - const cGetScpInjectProxyMethods = { - /** - * - * @param {Association} as - * @param {PresentationContext} pc - * @param {Attributes} rq - * @param {Attributes} keys - */ - calculateMatches: async (as, pc, rq, keys) => { - let instances = await getInstancesFromKeysAttr(keys); - if (await instances.isEmpty()) { - return null; - } - - let withoutBulkData = await (await this.scpObj.getQrLevels()).size() == 1; - let retrieveTask = await RetrieveTaskImpl.newInstanceAsync( - Dimse.C_GET_RQ, - as, - pc, - rq, - instances, - as, - withoutBulkData, - await this.getAuditInject(as), - 0 - ); - await retrieveTask.setSendPendingRSP(false); - - return retrieveTask; - } - }; - - return cGetScpInjectProxyMethods; - }; - - getAuditInject(association) { - let dimseRetrieveAuditService = new DimseRetrieveAuditService( - association, - null, - null - ); - - return createRetrieveAuditInjectProxy( - { - onBeginTransferringDICOMInstances: async (studyUIDs) => { - dimseRetrieveAuditService.studyUID = studyUIDs[0]; - await dimseRetrieveAuditService.onBeginRetrieve(); - }, - onDicomInstancesTransferred: async (studyUIDs) => { - dimseRetrieveAuditService.studyUID = studyUIDs[0]; - await dimseRetrieveAuditService.completedRetrieve(); - }, - setEventResult: (eventResult) => { - dimseRetrieveAuditService.eventResult = eventResult; - } - } - ); - } -} - -module.exports.JsCGetScp = JsCGetScp; \ No newline at end of file diff --git a/dimse-sql/c-move.js b/dimse-sql/c-move.js deleted file mode 100644 index f3f93a59..00000000 --- a/dimse-sql/c-move.js +++ /dev/null @@ -1,194 +0,0 @@ -const _ = require("lodash"); - -const { Attributes } = require("@dcm4che/data/Attributes"); -const { Tag } = require("@dcm4che/data/Tag"); -const { Association } = require("@dcm4che/net/Association"); -const { Status } = require("@dcm4che/net/Status"); -const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); -const { DicomServiceError } = require("@error/dicom-service"); -const { createCMoveSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CMoveSCPInject"); -const { SimpleCMoveSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCMoveSCP"); -const { UID } = require("@dcm4che/data/UID"); - -const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); -const { default: AAssociateRQ } = require("@dcm4che/net/pdu/AAssociateRQ"); -const { default: Connection } = require("@dcm4che/net/Connection"); -const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); -const { Dimse } = require("@dcm4che/net/Dimse"); -const { getInstancesFromKeysAttr } = require("./utils"); -const { DimseRetrieveAuditService } = require("@root/dimse/service/retrieveAudit.service"); -const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); - -class JsCMoveScp { - constructor(dcmQrScp) { - /** @type { import("./index").DcmQrScp } */ - this.dcmQrScp = dcmQrScp; - } - - getPatientRootLevel() { - const cMoveScpInject = createCMoveSCPInjectProxy(this.getCMoveScpInjectProxyMethods(), { - keepAsDaemon: true - }); - - return new SimpleCMoveSCP( - cMoveScpInject, - UID.PatientRootQueryRetrieveInformationModelMove, - PATIENT_ROOT_LEVELS - ); - } - - getStudyRootLevel() { - const cMoveScpInject = createCMoveSCPInjectProxy(this.getCMoveScpInjectProxyMethods(), { - keepAsDaemon: true - }); - - return new SimpleCMoveSCP( - cMoveScpInject, - UID.StudyRootQueryRetrieveInformationModelMove, - STUDY_ROOT_LEVELS - ); - } - - getPatientStudyOnlyLevel() { - const cMoveScpInject = createCMoveSCPInjectProxy(this.getCMoveScpInjectProxyMethods(), { - keepAsDaemon: true - }); - - return new SimpleCMoveSCP( - cMoveScpInject, - UID.PatientStudyOnlyQueryRetrieveInformationModelMove, - PATIENT_STUDY_ONLY_LEVELS - ); - } - - getCMoveScpInjectProxyMethods() { - /** @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/CMoveSCPInject").CMoveSCPInjectInterface } */ - const cMoveScpInjectProxyMethods = { - /** - * - * @param {Association} as - * @param {PresentationContext} pc - * @param {Attributes} rq - * @param {Attributes} keys - */ - calculateMatches: async (as, pc, rq, keys) => { - try { - let moveDest = await rq.getString(Tag.MoveDestination); - const remote = this.dcmQrScp.getRemoteConnection(moveDest); - if (!remote) { - throw new DicomServiceError(Status.MoveDestinationUnknown, `Move Destination: ${moveDest} unknown`); - } - - let instances = await getInstancesFromKeysAttr(keys); - if (await instances.isEmpty()) { - return null; - } - - let aAssociateRq = await this.makeAAssociateRQ_(await as.getLocalAET(), moveDest, instances); - let storeAssociation = await this.openStoreAssociation_(as, remote, aAssociateRq); - - let auditInject = await this.getAuditInject(as); - let retrieveTask = await RetrieveTaskImpl.newInstanceAsync( - Dimse.C_MOVE_RQ, - as, - pc, - rq, - instances, - storeAssociation, - false, - 0, - auditInject - ); - await retrieveTask.setSendPendingRSPInterval(0); - return retrieveTask; - } catch (e) { - console.error(e); - throw e; - } - } - }; - - return cMoveScpInjectProxyMethods; - } - - getAuditInject(association) { - let dimseRetrieveAuditService = new DimseRetrieveAuditService( - association, - null, - null - ); - if (this.auditInject) - return this.auditInject; - - this.auditInject = createRetrieveAuditInjectProxy( - { - onBeginTransferringDICOMInstances: async (studyUID) => { - dimseRetrieveAuditService.studyUID = studyUID; - await dimseRetrieveAuditService.onBeginRetrieve(); - }, - onDicomInstancesTransferred: async (studyUID) => { - dimseRetrieveAuditService.studyUID = studyUID; - await dimseRetrieveAuditService.completedRetrieve(); - }, - setEventResult: (eventResult) => { - dimseRetrieveAuditService.eventResult = eventResult; - } - }, - { - keepAsDaemon: true - } - ); - - return this.auditInject; - } - - /** - * @private - * @param {string} callingAet - * @param {string} calledAet - * @param {*} matches - */ - async makeAAssociateRQ_(callingAet, calledAet, matches) { - let aAssociateRq = await AAssociateRQ.newInstanceAsync(); - await aAssociateRq.setCallingAET(callingAet); - await aAssociateRq.setCalledAET(calledAet); - let matchesArray = await matches.toArray(); - for (let match of matchesArray) { - if (await aAssociateRq.addPresentationContextFor(match.cuid, match.tsuid)) { - - if (!UID.ExplicitVRLittleEndian === match.tsuid) { - await aAssociateRq.addPresentationContextFor(match.cuid, UID.ExplicitVRLittleEndian); - } - - if (!UID.ImplicitVRLittleEndian === match.tsuid) { - await aAssociateRq.addPresentationContextFor(match.cuid, UID.ImplicitVRLittleEndian); - } - } - } - return aAssociateRq; - } - - /** - * @private - * @param {Association} as - * @param {Connection} remote - * @param {AAssociateRQ} aAssociateRq - * @returns - */ - async openStoreAssociation_(as, remote, aAssociateRq) { - try { - let applicationEntity = await as.getApplicationEntity(); - - return await applicationEntity.connect( - await as.getConnection(), - remote, - aAssociateRq - ); - - } catch (e) { - throw new DicomServiceError(Status.UnableToPerformSubOperations, e); - } - } -} - -module.exports.JsCMoveScp = JsCMoveScp; \ No newline at end of file diff --git a/dimse-sql/c-store.js b/dimse-sql/c-store.js deleted file mode 100644 index bb8c1195..00000000 --- a/dimse-sql/c-store.js +++ /dev/null @@ -1,49 +0,0 @@ -const path = require("path"); -const { createCStoreSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CStoreSCPInject"); -const { default: SimpleCStoreSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCStoreSCP"); -const { default: File } = require("@java-wrapper/java/io/File"); -const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); - -const cStoreScpInjectProxy = createCStoreSCPInjectProxy({ - postDimseRQ: async (association, presentationContext, dimse, requestAttr, data, responseAttr) => { - await association.tryWriteDimseRSP(presentationContext, responseAttr); - }, - postStore: async (association, presentationContext, requestAttr, data, responseAttr, file) => { - let absPath = await file.getAbsolutePath(); - - let stowRsService = new StowRsService({ - headers: { - host: "fake-host" - }, - params: {} - }, []); - - /** @type {formidable.File} */ - let fileObj = { - filepath: path.resolve(absPath), - originalFilename: path.basename(absPath) - }; - - try { - await stowRsService.storeInstance(fileObj); - } catch (e) { - throw e; - } - } -}, { - keepAsDaemon: true -}); - -class JsCStoreScp { - constructor() {} - - get() { - let storageDir = new File( - path.join(__dirname, "../tempUploadFiles") - ); - let basicCStoreScp = new SimpleCStoreSCP(cStoreScpInjectProxy, storageDir, ["*"]); - return basicCStoreScp; - } -} - -module.exports.JsCStoreScp = JsCStoreScp; \ No newline at end of file diff --git a/dimse-sql/index.js b/dimse-sql/index.js index b793d2b6..49196011 100644 --- a/dimse-sql/index.js +++ b/dimse-sql/index.js @@ -2,10 +2,10 @@ const { java } = require("@models/DICOM/dcm4che/java-instance"); const { BasicCEchoSCP } = require("@dcm4che/net/service/BasicCEchoSCP"); const { DicomServiceRegistry } = require("@dcm4che/net/service/DicomServiceRegistry"); -const { JsCStoreScp } = require("./c-store"); -const { SqlJsCFindScp: JsCFindScp } = require("./c-find"); -const { JsCMoveScp } = require("./c-move"); -const { JsCGetScp } = require("./c-get"); +const { JsCStoreScp } = require("../dimse/c-store"); +const { JsCFindScp } = require("../dimse/c-find"); +const { JsCMoveScp } = require("../dimse/c-move"); +const { JsCGetScp } = require("../dimse/c-get"); const { DcmQrScp } = require("@root/dimse"); class SqlDcmQrScp extends DcmQrScp { diff --git a/dimse-sql/instanceQueryTask.js b/dimse-sql/instanceQueryTask.js index b48400e0..ec1cf106 100644 --- a/dimse-sql/instanceQueryTask.js +++ b/dimse-sql/instanceQueryTask.js @@ -1,14 +1,10 @@ const _ = require("lodash"); -const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); -const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); const { JsSeriesQueryTask } = require("./seriesQueryTask"); const { InstanceQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTask"); const { Attributes } = require("@dcm4che/data/Attributes"); -const { createInstanceQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTaskInject"); const { InstanceModel } = require("@models/sql/models/instance.model"); const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); -const { Tag } = require("@dcm4che/data/Tag"); const { InstanceQueryTaskInjectProxy, InstanceMatchIteratorProxy } = require("@root/dimse/instanceQueryTask"); const { QueryTaskUtils } = require("@root/dimse/utils"); diff --git a/dimse-sql/level.js b/dimse-sql/level.js deleted file mode 100644 index 83fd89c8..00000000 --- a/dimse-sql/level.js +++ /dev/null @@ -1,24 +0,0 @@ -const { EnumSet } = require("@java-wrapper/java/util/EnumSet"); -const { QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); - -const PATIENT_ROOT_LEVELS = EnumSet.ofSync( - QueryRetrieveLevel2.PATIENT, - QueryRetrieveLevel2.STUDY, - QueryRetrieveLevel2.SERIES, - QueryRetrieveLevel2.IMAGE -); - -const STUDY_ROOT_LEVELS = EnumSet.ofSync( - QueryRetrieveLevel2.STUDY, - QueryRetrieveLevel2.SERIES, - QueryRetrieveLevel2.IMAGE -); - -const PATIENT_STUDY_ONLY_LEVELS = EnumSet.ofSync( - QueryRetrieveLevel2.PATIENT, - QueryRetrieveLevel2.STUDY -); - -module.exports.PATIENT_ROOT_LEVELS = PATIENT_ROOT_LEVELS; -module.exports.STUDY_ROOT_LEVELS = STUDY_ROOT_LEVELS; -module.exports.PATIENT_STUDY_ONLY_LEVELS = PATIENT_STUDY_ONLY_LEVELS; \ No newline at end of file diff --git a/dimse-sql/queryTagsOfEachLevel.js b/dimse-sql/queryTagsOfEachLevel.js deleted file mode 100644 index f6ac4a96..00000000 --- a/dimse-sql/queryTagsOfEachLevel.js +++ /dev/null @@ -1,33 +0,0 @@ -const { default: Tag } = require("@dcm4che/data/Tag"); - -const queryTagsOfEachLevel = { - "patient": [ - Tag.PatientName, - Tag.PatientID, - Tag.PatientBirthDate - ], - "study": [ - Tag.PatientID, - Tag.StudyInstanceUID, - Tag.StudyDate, - Tag.StudyTime, - Tag.AccessionNumber - ], - "series": [ - Tag.PatientID, - Tag.StudyInstanceUID, - Tag.SeriesInstanceUID, - Tag.Modality, - Tag.SeriesNumber - ], - "instance": [ - Tag.PatientID, - Tag.StudyInstanceUID, - Tag.SeriesInstanceUID, - Tag.SOPInstanceUID, - Tag.SOPClassUID, - Tag.InstanceNumber - ] -}; - -module.exports.queryTagsOfEachLevel = queryTagsOfEachLevel; \ No newline at end of file diff --git a/dimse-sql/stgcmt.js b/dimse-sql/stgcmt.js deleted file mode 100644 index cab983d4..00000000 --- a/dimse-sql/stgcmt.js +++ /dev/null @@ -1,149 +0,0 @@ -const path = require("path"); -const { Commands } = require("@dcm4che/net/Commands"); -const { Status } = require("@dcm4che/net/Status"); -const { DicomServiceError } = require("@error/dicom-service"); -const { createStgCmtSCPInjectProxy } = require("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/StgCmtSCPInject"); -const { Attributes } = require("@dcm4che/data/Attributes"); -const { Tag } = require("@dcm4che/data/Tag"); -const { VR } = require("@dcm4che/data/VR"); -const { raccoonConfig } = require("@root/config-class"); -const { findOneInstanceFromKeysAttr } = require("./utils"); -const fileExist = require("@root/utils/file/fileExist"); -const { SimpleStgCmtSCP } = require("@chinlinlee/dcm777/net/SimpleStgCmtSCP"); -const { SendStgCmtResult } = require("@chinlinlee/dcm777/dcmqrscp/SendStgCmtResult"); - -class JsStgCmtScp { - constructor(dcmQrScp) { - /** @type { import("./index").DcmQrScp } */ - this.dcmQrScp = dcmQrScp; - } - - get() { - return new SimpleStgCmtSCP( - this.getStgCmtInjectProxy() - ); - } - - getStgCmtInjectProxy() { - /** @type { import("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/StgCmtSCPInject").StgCmtSCPInjectInterface } */ - const stgCmtInjectProxyMethods = { - onDimseRQ: async (as, pc, dimse, rq, actionInfo) => { - let rsp = await Commands.mkNActionRSP(rq, Status.Success); - let callingAet = await as.getCallingAET(); - let calledAet = await as.getCalledAET(); - - let remoteConnection = this.dcmQrScp.getRemoteConnection(callingAet); - if (!remoteConnection) - throw new DicomServiceError(Status.ProcessingFailure, `Unknown Calling AET: ${callingAet}`); - - let eventInfo; - try { - eventInfo = await this.calculateStorageCommitmentResult(calledAet, actionInfo); - } catch(e) { - console.error(e); - throw e; - } - - try { - await as.writeDimseRSP(pc, rsp, null); - - await this.dcmQrScp.device.execute( - await SendStgCmtResult.newInstanceAsync( - as, - eventInfo, - false, - remoteConnection - ) - ); - } catch(e) { - console.error(`${await as.toString()} << N-ACTION-RSP failed: ${e}`); - } - } - }; - - return createStgCmtSCPInjectProxy(stgCmtInjectProxyMethods, { - keepAsDaemon: true - }); - } - - /** - * - * @param {string} calledAet - * @param {Attributes} actionInfo - * @returns { Promise } - */ - async calculateStorageCommitmentResult(calledAet, actionInfo) { - let requestReq = await actionInfo.getSequence(Tag.ReferencedSOPSequence); - let size = await requestReq.size(); - - let eventInfo = await Attributes.newInstanceAsync(6); - - await eventInfo.setString(Tag.RetrieveAETitle, VR.AE, calledAet); - await eventInfo.setString(Tag.StorageMediaFileSetID, VR.SH, raccoonConfig.mediaStorageID); - await eventInfo.setString(Tag.StorageMediaFileSetUID, VR.SH, raccoonConfig.mediaStorageUID); - await eventInfo.setString(Tag.TransactionUID, VR.UI, await actionInfo.getString(Tag.TransactionUID)); - let successSeq = await eventInfo.newSequence(Tag.ReferencedSOPSequence, size); - let failedSeq = await eventInfo.newSequence(Tag.FailedSOPSequence, size); - - let uidMap = {}; - for (let i = 0; i < size; i++) { - /** @type { Attributes } */ - let item = await requestReq.get(i); - uidMap[await item.getString(Tag.ReferencedSOPInstanceUID)] = await item.getString(Tag.ReferencedSOPClassUID); - } - - for (let key in uidMap) { - let classUid = uidMap[key]; - let attr = await Attributes.newInstanceAsync(); - await attr.setString(Tag.SOPInstanceUID, VR.UI, key); - await attr.setString(Tag.SOPClassUID, VR.UI, classUid); - - let instance = await findOneInstanceFromKeysAttr(attr); - if (instance) { - let isExist = await fileExist( - path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - instance.instancePath - ) - ); - if (isExist) { - await successSeq.add( - await JsStgCmtScp.refSOP(key, classUid, Status.Success) - ); - } else { - await failedSeq.add( - await JsStgCmtScp.refSOP(key, classUid, Status.NoSuchObjectInstance) - ); - } - } else { - await failedSeq.add( - await JsStgCmtScp.refSOP(key, classUid, Status.NoSuchObjectInstance) - ); - } - } - - if (await failedSeq.isEmpty()) - await eventInfo.remove(Tag.FailedSOPSequence); - - return eventInfo; - } - - /** - * @private - * @param {string} instanceUid - * @param {string} classUid - * @param {number} failureReason - * @returns { Promise } - */ - static async refSOP(instanceUid, classUid, failureReason) { - let attr = await Attributes.newInstanceAsync(3); - await attr.setString(Tag.ReferencedSOPClassUID, VR.UI, classUid); - await attr.setString(Tag.ReferencedSOPInstanceUID, VR.UI, instanceUid); - if (failureReason !== Status.Success) { - await attr.setInt(Tag.FailureReason, VR.US, failureReason); - } - return attr; - } -} - -module.exports.JsStgCmtScp = JsStgCmtScp; \ No newline at end of file diff --git a/dimse-sql/utils.js b/dimse-sql/utils.js index 643932f2..a090c17c 100644 --- a/dimse-sql/utils.js +++ b/dimse-sql/utils.js @@ -106,4 +106,5 @@ QueryTaskUtils.getDbQuery = async function (queryAttr, level = "patient") { module.exports.intTagToString = intTagToString; module.exports.getInstancesFromKeysAttr = getInstancesFromKeysAttr; -module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr; \ No newline at end of file +module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr; +module.exports.QueryTaskUtils = QueryTaskUtils; \ No newline at end of file diff --git a/dimse/c-find.js b/dimse/c-find.js index c519dfaa..1bd5c061 100644 --- a/dimse/c-find.js +++ b/dimse/c-find.js @@ -7,10 +7,10 @@ const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); const { QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); const { BasicModCFindSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP"); const { createCFindSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject"); -const { JsPatientQueryTask } = require("./patientQueryTask"); -const { JsStudyQueryTask } = require("./studyQueryTask"); -const { JsSeriesQueryTask } = require("./seriesQueryTask"); -const { JsInstanceQueryTask } = require("./instanceQueryTask"); +const { JsPatientQueryTask } = require("@dimse-patient-query-task"); +const { JsStudyQueryTask } = require("@dimse-study-query-task"); +const { JsSeriesQueryTask } = require("@dimse-series-query-task"); +const { JsInstanceQueryTask } = require("@dimse-instance-query-task"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); class JsCFindScp { diff --git a/dimse/c-get.js b/dimse/c-get.js index fbeb0f0a..3b1ff5bc 100644 --- a/dimse/c-get.js +++ b/dimse/c-get.js @@ -2,7 +2,7 @@ const { UID } = require("@dcm4che/data/UID"); const { createCGetSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CGetSCPInject"); const { SimpleCGetSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCGetSCP"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); -const { getInstancesFromKeysAttr } = require("./utils"); +const { getInstancesFromKeysAttr } = require("@dimse-utils"); const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); const { Dimse } = require("@dcm4che/net/Dimse"); diff --git a/dimse/c-move.js b/dimse/c-move.js index 8a8d0bc3..e56215b2 100644 --- a/dimse/c-move.js +++ b/dimse/c-move.js @@ -13,7 +13,7 @@ const { AAssociateRQ } = require("@dcm4che/net/pdu/AAssociateRQ"); const { Connection } = require("@dcm4che/net/Connection"); const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); const { Dimse } = require("@dcm4che/net/Dimse"); -const { getInstancesFromKeysAttr } = require("./utils"); +const { getInstancesFromKeysAttr } = require("@dimse-utils"); const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); const { DimseRetrieveAuditService } = require("./service/retrieveAudit.service"); diff --git a/dimse/c-store.js b/dimse/c-store.js index cf592dbe..6461da5c 100644 --- a/dimse/c-store.js +++ b/dimse/c-store.js @@ -2,7 +2,7 @@ const path = require("path"); const { createCStoreSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CStoreSCPInject"); const { default: SimpleCStoreSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCStoreSCP"); const { default: File } = require("@java-wrapper/java/io/File"); -const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service"); +const { StowRsService } = require("@stow-rs-service"); const { default: Association } = require("@dcm4che/net/Association"); const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); const { Attributes } = require("@dcm4che/data/Attributes"); From d870b7b20743052abc6affa19066a278a171577c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 09:59:34 +0800 Subject: [PATCH 169/365] refactor: rename `workItems.js` to `workItems.model.js` --- api/dicom-web/controller/UPS-RS/service/cancel.service.js | 2 -- .../UPS-RS/service/change-workItem-state.service.js | 4 ++-- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- .../controller/UPS-RS/service/get-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/subscribe.service.js | 2 +- .../controller/UPS-RS/service/unsubscribe.service.js | 2 +- .../controller/UPS-RS/service/update-workItem.service.js | 2 +- models/mongodb/models/{workItems.js => workItems.model.js} | 1 + 8 files changed, 8 insertions(+), 9 deletions(-) rename models/mongodb/models/{workItems.js => workItems.model.js} (98%) diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index 08122b32..f9833516 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -1,7 +1,5 @@ const _ = require("lodash"); -const workItemModel = require("@models/mongodb/models/workItems"); const { DicomJsonModel, BaseDicomJson } = require("@dicom-json-model"); -const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { DicomWebServiceError, DicomWebStatusCodes diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index 44e7f6ca..dba67314 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const moment = require("moment"); const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const workItemModel = require("@models/mongodb/models/workItems"); +const { WorkItemModel } = require("@models/mongodb/models/workItems.model"); const { DicomWebServiceError, DicomWebStatusCodes @@ -51,7 +51,7 @@ class ChangeWorkItemStateService extends BaseWorkItemService { this.completeChange(); } - let updatedWorkItem = await workItemModel.findOneAndUpdate({ + let updatedWorkItem = await WorkItemModel.findOneAndUpdate({ upsInstanceUID: this.request.params.workItem }, { ...this.requestState.dicomJson diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 186ea403..e2c220b1 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const workItemModel = require("@models/mongodb/models/workItems"); +const workItemModel = require("@models/mongodb/models/workItems.model"); const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { diff --git a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 558a6b49..4f92f0b0 100644 --- a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const workItemsModel = require("@models/mongodb/models/workItems"); +const workItemsModel = require("@models/mongodb/models/workItems.model"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index f50593f1..ec3d0e47 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const workItemModel = require("@models/mongodb/models/workItems"); +const workItemModel = require("@models/mongodb/models/workItems.model"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 51fa0e82..1e2bb664 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const workItemModel = require("@models/mongodb/models/workItems"); +const workItemModel = require("@models/mongodb/models/workItems.model"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index b1b33d8e..5ae5849c 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const workItemModel = require("@models/mongodb/models/workItems"); +const workItemModel = require("@models/mongodb/models/workItems.model"); const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { diff --git a/models/mongodb/models/workItems.js b/models/mongodb/models/workItems.model.js similarity index 98% rename from models/mongodb/models/workItems.js rename to models/mongodb/models/workItems.model.js index bea22c29..67766951 100644 --- a/models/mongodb/models/workItems.js +++ b/models/mongodb/models/workItems.model.js @@ -119,3 +119,4 @@ let workItemModel = mongoose.model( /** @type { WorkItemsModel } */ module.exports = workItemModel; +module.exports.WorkItemModel = workItemModel; From 80fb3b8ccc8c74a30688dd524b15cfaac1e61389 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 10:17:21 +0800 Subject: [PATCH 170/365] feat: change all string type length to 255 for each vr --- models/sql/vrTypeMapping.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/models/sql/vrTypeMapping.js b/models/sql/vrTypeMapping.js index e1627351..9ecd7349 100644 --- a/models/sql/vrTypeMapping.js +++ b/models/sql/vrTypeMapping.js @@ -2,16 +2,16 @@ const { raccoonConfig } = require("@root/config-class"); const { DataTypes } = require("sequelize"); const vrTypeMapping = { - "AE": DataTypes.STRING(16+1), - "AS": DataTypes.STRING(4+1), - "CS": DataTypes.STRING(16+1), + "AE": DataTypes.STRING, + "AS": DataTypes.STRING, + "CS": DataTypes.STRING, "DA": DataTypes.DATEONLY, - "DS": DataTypes.STRING(16+1), + "DS": DataTypes.STRING, "DT": DataTypes.DATE, "FT": DataTypes.FLOAT, "FD": DataTypes.DOUBLE, - "IS": DataTypes.STRING(12+1), - "LO": DataTypes.STRING(64+1), + "IS": DataTypes.STRING, + "LO": DataTypes.STRING, "LT": DataTypes.STRING(10240+1), "OB": DataTypes.TEXT, "OD": DataTypes.TEXT, @@ -20,14 +20,14 @@ const vrTypeMapping = { "OV": DataTypes.TEXT, "OW": DataTypes.TEXT, "PN": DataTypes.INTEGER, // foreign key - "SH": DataTypes.STRING(16+1), + "SH": DataTypes.STRING, "SL": DataTypes.INTEGER, "SS": DataTypes.SMALLINT, "ST": DataTypes.STRING(1024+1), "SV": DataTypes.BIGINT, "TM": DataTypes.DECIMAL, "UC": DataTypes.TEXT("long"), - "UI": DataTypes.STRING(64+1), + "UI": DataTypes.STRING, "UL": DataTypes.INTEGER.UNSIGNED, "UR": DataTypes.TEXT("long"), "US": DataTypes.SMALLINT.UNSIGNED, From 83f5dd8c6f70e728e4a5dd5ef6c95e2f523def6a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 10:38:32 +0800 Subject: [PATCH 171/365] feat: add UPS work item model --- models/sql/models/workitems.model.js | 109 +++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 models/sql/models/workitems.model.js diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js new file mode 100644 index 00000000..2dcaf5e8 --- /dev/null +++ b/models/sql/models/workitems.model.js @@ -0,0 +1,109 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); +const { raccoonConfig } = require("@root/config-class"); +const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); + +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + +class WorkItemModel extends Model { + + + async getAttributes() { + let obj = this.toJSON(); + let jsonStr = JSON.stringify(obj.json); + return await Common.getAttributesFromJsonString(jsonStr); + } +}; + +WorkItemModel.init({ + upsInstanceUID: { + type: DataTypes.STRING, + allowNull: false + }, + patientID: { + type: DataTypes.STRING, + allowNull: false + }, + transactionUID: { + type: DataTypes.STRING + }, + subscribed: { + type: DataTypes.INTEGER, + default: SUBSCRIPTION_STATE.NOT_SUBSCRIBED + }, + //#region patient level + "x00100010": { + type: DataTypes.INTEGER + }, + "x00100020": { + type: vrTypeMapping.LO + }, + "x00100021": { + type: vrTypeMapping.LO + }, + "x00100040": { + type: vrTypeMapping.CS + }, + //#endregion + "x00080018": { + type: vrTypeMapping.UI + }, + "x00741200": { + type: vrTypeMapping.CS + }, + "x00404010": { + type: vrTypeMapping.DT + }, + "x00741204": { + type: vrTypeMapping.LO + }, + "x00741202": { + type: vrTypeMapping.LO + }, + "x00404025": { + // TODO: DICOM Code Model, and do reference + type: DataTypes.INTEGER + }, + "x00404026": { + // TODO: DICOM Code Model, and do reference + type: DataTypes.INTEGER + }, + "x00404027": { + // TODO: DICOM Code Model, and do reference + type: DataTypes.INTEGER + }, + "x00404034": { + // TODO: DICOM Code Model, and do reference + type: DataTypes.INTEGER + }, + "x00404005": { + type: vrTypeMapping.DT + }, + "x00404011": { + type: vrTypeMapping.DT + }, + "x00380010": { + type: vrTypeMapping.LO + }, + "x00741000": { + type: vrTypeMapping.CS + }, + "x00080082": { + type: DataTypes.INTEGER + }, + "json": { + type: vrTypeMapping.JSON + } +}, { + sequelize: sequelizeInstance, + modelName: "UPSWorkItem", + tableName: "UPSWorkItem", + freezeTableName: true +}); + +module.exports.WorkItemModel = WorkItemModel; From 031f72b7c6073970840be8cc69e96586591e369c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 11:14:06 +0800 Subject: [PATCH 172/365] feat: add module alias for mongodb --- config/modula-alias/mongodb/package.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/config/modula-alias/mongodb/package.json b/config/modula-alias/mongodb/package.json index a14c6209..7ff04ef5 100644 --- a/config/modula-alias/mongodb/package.json +++ b/config/modula-alias/mongodb/package.json @@ -8,6 +8,20 @@ "@chinlinlee": "../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee", "@dbModels": "../../../models/mongodb/models", "@dbInitializer": "../../../models/mongodb/index.js", - "@dicom-json-model": "../../../models/DICOM/dicom-json-model.js" + "@dicom-json-model": "../../../models/DICOM/dicom-json-model.js", + "@query-dicom-json-factory": "../../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js", + "@stow-rs-service": "../../../api/dicom-web/controller/STOW-RS/service/stow-rs.service.js", + "@qido-rs-service": "../../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", + "@wado-rs-service": "../../../api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", + "@bulkdata-service": "../../../api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", + "@delete-service": "../../../api/dicom-web/controller/WADO-RS/deletion/service/delete.js", + "@rendered-service": "../../../api/dicom-web/controller/WADO-RS/service/rendered.service.js", + "@thumbnail-service": "../../../api/dicom-web/controller/WADO-RS/service/thumbnail.service.js", + "@dimse-query-builder": "../../../dimse/queryBuilder.js", + "@dimse-patient-query-task": "../../../dimse/patientQueryTask.js", + "@dimse-study-query-task": "../../../dimse/studyQueryTask.js", + "@dimse-series-query-task": "../../../dimse/seriesQueryTask.js", + "@dimse-instance-query-task": "../../../dimse/instanceQueryTask.js", + "@dimse-utils": "../../../dimse/utils.js" } } \ No newline at end of file From 57aaea7f232d415c6c8cd9bbfcda9b230e2ec33e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 11:28:27 +0800 Subject: [PATCH 173/365] refactor: extract processes to methods for `createUps` in create-workItem.service.js This commit simplifies the code in create-workItem.service.js by refactoring and organizing the methods. The changes include extracting the data adjustment before creating the work item into a separate method, validating the work item in another method, and triggering the create event in a new async function. The code has been restructured to improve readability and maintainability while avoiding code duplication. --- .../UPS-RS/service/create-workItem.service.js | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index e2c220b1..f715a93a 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -24,6 +24,19 @@ class CreateWorkItemService extends BaseWorkItemService { let uid = _.get(this.request, "query.workitem", await UIDUtils.createUID() ); + this.dataAdjustBeforeCreatingUps(uid); + this.validateWorkItem(uid); + + let patient = await this.findOneOrCreatePatient(); + let workItem = new workItemModel(this.requestWorkItem.dicomJson); + let savedWorkItem = await workItem.save(); + + this.triggerCreateEvent(savedWorkItem); + + return workItem; + } + + async dataAdjustBeforeCreatingUps(uid) { _.set(this.requestWorkItem.dicomJson, "upsInstanceUID", uid); _.set(this.requestWorkItem.dicomJson, "00080018", { vr: "UI", @@ -40,7 +53,9 @@ class CreateWorkItemService extends BaseWorkItemService { ] }); } + } + async validateWorkItem(uid) { if (this.requestWorkItem.getString("00741000") !== "SCHEDULED") { throw new DicomWebServiceError( DicomWebStatusCodes.UPSNotScheduled, @@ -49,10 +64,6 @@ class CreateWorkItemService extends BaseWorkItemService { ); } - let patient = await this.findOneOrCreatePatient(); - - let workItem = new workItemModel(this.requestWorkItem.dicomJson); - if (await this.isUpsExist(uid)) { throw new DicomWebServiceError( DicomWebStatusCodes.DuplicateSOPinstance, @@ -60,12 +71,12 @@ class CreateWorkItemService extends BaseWorkItemService { 400 ); } - let savedWorkItem = await workItem.save(); - + } - let workItemDicomJson = new DicomJsonModel(savedWorkItem); + async triggerCreateEvent(workItem) { + let workItemDicomJson = new DicomJsonModel(workItem); let hitGlobalSubscriptions = await this.getHitGlobalSubscriptions(workItemDicomJson); - for(let hitGlobalSubscription of hitGlobalSubscriptions) { + for (let hitGlobalSubscription of hitGlobalSubscriptions) { let subscribeService = new SubscribeService(this.request, this.response); subscribeService.upsInstanceUID = workItemDicomJson.dicomJson.upsInstanceUID; subscribeService.deletionLock = hitGlobalSubscription.isDeletionLock; @@ -74,7 +85,7 @@ class CreateWorkItemService extends BaseWorkItemService { } let hitSubscriptions = await this.getHitSubscriptions(workItemDicomJson); - + if (hitSubscriptions) { let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItemDicomJson), hitSubscriptionAeTitleArray); @@ -83,15 +94,13 @@ class CreateWorkItemService extends BaseWorkItemService { _.get(workItemDicomJson.dicomJson, `${dictionary.keyword.ScheduledStationNameCodeSequence}`, false), _.get(workItemDicomJson.dicomJson, `${dictionary.keyword.ScheduledHumanPerformersSequence}`, false) ); - - for(let assignedEventInfo of assignedEventInformationArray) { + + for (let assignedEventInfo of assignedEventInformationArray) { this.addUpsEvent(UPS_EVENT_TYPE.Assigned, workItemDicomJson.dicomJson.upsInstanceUID, assignedEventInfo, hitSubscriptionAeTitleArray); } } - - this.triggerUpsEvents(); - return workItem; + this.triggerUpsEvents(); } async findOneOrCreatePatient() { From ebefd6acef226c522f91f5c56b68052cf159377d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 13:09:24 +0800 Subject: [PATCH 174/365] refactor: Use module alias for WADO-URI service --- .../WADO-URI/controller/retrieveInstance.js | 55 ------------------- api-sql/WADO-URI/index.js | 45 --------------- api/WADO-URI/controller/retrieveInstance.js | 2 +- config/jsconfig.sql.json | 1 + config/modula-alias/sql/package.json | 4 +- routes.js | 2 +- 6 files changed, 6 insertions(+), 103 deletions(-) delete mode 100644 api-sql/WADO-URI/controller/retrieveInstance.js delete mode 100644 api-sql/WADO-URI/index.js diff --git a/api-sql/WADO-URI/controller/retrieveInstance.js b/api-sql/WADO-URI/controller/retrieveInstance.js deleted file mode 100644 index 5224425d..00000000 --- a/api-sql/WADO-URI/controller/retrieveInstance.js +++ /dev/null @@ -1,55 +0,0 @@ -const { WadoUriService, NotFoundInstanceError } = require("../service/WADO-URI.service"); -const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); - -class RetrieveSingleInstanceController extends Controller { - constructor(req, res) { - super(req, res); - this.logger = new ApiLogger(this.request, "WADO-URI"); - this.service = new WadoUriService(req, res, this.logger); - } - - async mainProcess() { - let { - contentType - } = this.request.query; - - if (!contentType) contentType = this.request.headers.accept; - - try { - - if (contentType === "application/dicom") { - this.service.getAndResponseDicomInstance(); - } else if (contentType === "image/jpeg") { - this.service.getAndResponseJpeg(); - } else if (!contentType) { - this.service.getAndResponseDicomInstance(); - } - - } catch(e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - this.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify({ - code: 500, - message: errorStr - })); - } - - } - -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveSingleInstanceController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api-sql/WADO-URI/index.js b/api-sql/WADO-URI/index.js deleted file mode 100644 index 11330b89..00000000 --- a/api-sql/WADO-URI/index.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Route - * Implement https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_9.4 - * WADO-URI - * @author Chin-Lin Lee - */ - -const { wadoUriValidationSchema } = require("@root/api/WADO-URI/middleware/validation-schema"); -const { defaultContentType } = require("@root/api/WADO-URI/middleware/default-contentType"); -const { validateByJoi} = require("@root/api/validator"); -const express = require("express"); -const router = express.Router(); - -/** - * @openapi - * /wado: - * get: - * tags: - * - WADO-URI - * description: Retrieve instance's metadata - * parameters: - * - $ref: "#/components/parameters/requestType" - * - $ref: "#/components/parameters/queryStudyUID" - * - $ref: "#/components/parameters/querySeriesUID" - * - $ref: "#/components/parameters/queryInstanceUID" - * - $ref: "#/components/parameters/contentType" - * - $ref: "#/components/parameters/frameNumber" - * - $ref: "#/components/parameters/imageQuality" - * - $ref: "#/components/parameters/region" - * - $ref: "#/components/parameters/rows" - * - $ref: "#/components/parameters/columns" - * - $ref: "#/components/parameters/windowCenter" - * - $ref: "#/components/parameters/windowWidth" - * - $ref: "#/components/parameters/iccprofile" - * responses: - * 200: - * $ref: "#/components/responses/WadoUriData" - * - */ -router.get("/", defaultContentType, validateByJoi(wadoUriValidationSchema, "query", { - allowUnknown: false -}), require("./controller/retrieveInstance")); - - -module.exports = router; \ No newline at end of file diff --git a/api/WADO-URI/controller/retrieveInstance.js b/api/WADO-URI/controller/retrieveInstance.js index efb12700..4af01fba 100644 --- a/api/WADO-URI/controller/retrieveInstance.js +++ b/api/WADO-URI/controller/retrieveInstance.js @@ -1,4 +1,4 @@ -const { WadoUriService, NotFoundInstanceError } = require("../service/WADO-URI.service"); +const { WadoUriService, NotFoundInstanceError } = require("@wado-uri-service"); const { Controller } = require("../../controller.class"); const { ApiLogger } = require("../../../utils/logs/api-logger"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index a8d0a396..e5a230d8 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -14,6 +14,7 @@ "@stow-rs-service": ["./api/dicom-web/controller/STOW-RS/service/stow-rs.service.js"], "@qido-rs-service": ["./api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], "@wado-rs-service": ["./api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], + "@wado-uri-service": ["./api-sql/WADO-URI/service/WADO-URI.service.js"], "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"], "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"], diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index 52a9b3cb..7aa0693e 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -13,6 +13,7 @@ "@stow-rs-service": "../../../api/dicom-web/controller/STOW-RS/service/stow-rs.service.js", "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", "@wado-rs-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", + "@wado-uri-service": "../../../api-sql/WADO-URI/service/WADO-URI.service.js", "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js", "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js", @@ -22,6 +23,7 @@ "@dimse-study-query-task": "../../../dimse-sql/studyQueryTask.js", "@dimse-series-query-task": "../../../dimse-sql/seriesQueryTask.js", "@dimse-instance-query-task": "../../../dimse-sql/instanceQueryTask.js", - "@dimse-utils": "../../../dimse-sql/utils.js" + "@dimse-utils": "../../../dimse-sql/utils.js", + "@dimse": "../../../dimse-sql" } } \ No newline at end of file diff --git a/routes.js b/routes.js index 926f1329..ee4626ba 100644 --- a/routes.js +++ b/routes.js @@ -29,5 +29,5 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/delete.route")); // app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); - app.use("/wado", require("./api-sql/WADO-URI")); + app.use("/wado", require("./api/WADO-URI")); }; From 430e2cc5a19cec13d9ae5428b33231a05cd88d5b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 13:09:47 +0800 Subject: [PATCH 175/365] fix: missing await for async function --- .../controller/UPS-RS/service/create-workItem.service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index f715a93a..ab552105 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -24,9 +24,9 @@ class CreateWorkItemService extends BaseWorkItemService { let uid = _.get(this.request, "query.workitem", await UIDUtils.createUID() ); - this.dataAdjustBeforeCreatingUps(uid); - this.validateWorkItem(uid); - + await this.dataAdjustBeforeCreatingUps(uid); + await this.validateWorkItem(uid); + let patient = await this.findOneOrCreatePatient(); let workItem = new workItemModel(this.requestWorkItem.dicomJson); let savedWorkItem = await workItem.save(); From 20bf8df26b4e5a9970a8aac1a220f1b7efc277b9 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 13:10:36 +0800 Subject: [PATCH 176/365] fix: incorrect import statement for workItemModel --- .../controller/UPS-RS/service/base-workItem.service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index ec356ad5..1aad629c 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -6,7 +6,7 @@ const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); -const workItemModel = require("@dbModels/workItems"); +const { WorkItemModel } = require("@dbModels/workItems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); class BaseWorkItemService { @@ -115,7 +115,7 @@ class BaseWorkItemService { $match.$and.push({ upsInstanceUID: workItem.dicomJson.upsInstanceUID }); - let count = await workItemModel.countDocuments({ + let count = await WorkItemModel.countDocuments({ ...$match }); if (count > 0) @@ -167,7 +167,7 @@ class BaseWorkItemService { * @returns */ async findOneWorkItem(upsInstanceUID, toObject=false) { - let workItem = await workItemModel.findOne({ + let workItem = await WorkItemModel.findOne({ upsInstanceUID: upsInstanceUID }); From dcfe3aaa80d975a32a4745c9f9b3f4d46324511d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 13:12:49 +0800 Subject: [PATCH 177/365] feat: support switch to mongodb mode with module alias --- config-class.js | 5 ++-- config/jsconfig.mongodb.json | 17 ++++++++++++- config/modula-alias/mongodb/package.json | 4 ++- models/mongodb/connector.js | 2 +- server.js | 31 ++++++++++++++++++------ 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/config-class.js b/config-class.js index 3282ec07..fe7e2c94 100644 --- a/config-class.js +++ b/config-class.js @@ -33,6 +33,7 @@ class SqlDbConfig { this.username = env.get("SQL_USERNAME").default("postgres").asString(); this.password = env.get("SQL_PASSWORD").default("postgres").asString(); this.logging = env.get("SQL_LOGGING").default("false").asBool(); + this.dbName = this.database; } } @@ -92,11 +93,11 @@ class RaccoonConfig { /** @type {string} */ this.mediaStorageUID = generateUidFromGuid( - uuid.v5(this.dbConfig.database, NAME_SPACE) + uuid.v5(this.dbConfig.dbName, NAME_SPACE) ); /** @type {string} */ - this.mediaStorageID = this.dbConfig.database; + this.mediaStorageID = this.dbConfig.dbName; this.aeTitle = this.dicomWebConfig.aeTitle; // this.aeTitle = this.dicomDimseConfig.enableDimse ? this.dicomDimseConfig.getAeTitle() : this.dicomWebConfig.aeTitle; diff --git a/config/jsconfig.mongodb.json b/config/jsconfig.mongodb.json index 1151bfbb..970078b7 100644 --- a/config/jsconfig.mongodb.json +++ b/config/jsconfig.mongodb.json @@ -9,7 +9,22 @@ "@root/*": ["./*"], "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], "@dbModels/*": ["./models/mongodb/models/*"], - "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"] + "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"], + "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"], + "@stow-rs-service": ["./api/dicom-web/controller/STOW-RS/service/stow-rs.service.js"], + "@qido-rs-service": ["./api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], + "@wado-rs-service": ["./api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], + "@wado-uri-service": ["./api/WADO-URI/service/WADO-URI.service.js"], + "@bulkdata-service": ["./api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], + "@delete-service": ["./api/dicom-web/controller/WADO-RS/deletion/service/delete.js"], + "@rendered-service": ["./api/dicom-web/controller/WADO-RS/service/rendered.service.js"], + "@thumbnail-service": ["./api/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], + "@dimse-query-builder": ["./dimse/queryBuilder.js"], + "@dimse-patient-query-task": ["./dimse/patientQueryTask.js"], + "@dimse-study-query-task": ["./dimse/studyQueryTask.js"], + "@dimse-series-query-task": ["./dimse/seriesQueryTask.js"], + "@dimse-instance-query-task": ["./dimse/instanceQueryTask.js"], + "@dimse-utils": ["./dimse/utils.js"] } }, "exclude": [ diff --git a/config/modula-alias/mongodb/package.json b/config/modula-alias/mongodb/package.json index 7ff04ef5..6ab96a29 100644 --- a/config/modula-alias/mongodb/package.json +++ b/config/modula-alias/mongodb/package.json @@ -13,6 +13,7 @@ "@stow-rs-service": "../../../api/dicom-web/controller/STOW-RS/service/stow-rs.service.js", "@qido-rs-service": "../../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", "@wado-rs-service": "../../../api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", + "@wado-uri-service": "../../../api/WADO-URI/service/WADO-URI.service.js", "@bulkdata-service": "../../../api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", "@delete-service": "../../../api/dicom-web/controller/WADO-RS/deletion/service/delete.js", "@rendered-service": "../../../api/dicom-web/controller/WADO-RS/service/rendered.service.js", @@ -22,6 +23,7 @@ "@dimse-study-query-task": "../../../dimse/studyQueryTask.js", "@dimse-series-query-task": "../../../dimse/seriesQueryTask.js", "@dimse-instance-query-task": "../../../dimse/instanceQueryTask.js", - "@dimse-utils": "../../../dimse/utils.js" + "@dimse-utils": "../../../dimse/utils.js", + "@dimse": "../../../dimse" } } \ No newline at end of file diff --git a/models/mongodb/connector.js b/models/mongodb/connector.js index 9383af28..69d52fab 100644 --- a/models/mongodb/connector.js +++ b/models/mongodb/connector.js @@ -14,7 +14,7 @@ const { authSource, isShardingMode, urlOptions -} = raccoonConfig.mongoDbConfig; +} = raccoonConfig.dbConfig; module.exports = exports = function () { const collection = {}; diff --git a/server.js b/server.js index df5511c4..cc601547 100644 --- a/server.js +++ b/server.js @@ -7,7 +7,6 @@ if (raccoonConfig.serverConfig.dbType === "mongodb") { require('module-alias')(__dirname + "/config/modula-alias/sql"); } - const { app, server } = require("./app"); const bodyParser = require("body-parser"); const session = require("express-session"); @@ -15,11 +14,30 @@ const cookieParser = require("cookie-parser"); const compress = require("compression"); const cors = require("cors"); const os = require("os"); -const SequelizeStore = require("connect-session-sequelize")(session.Store); -const sequelizeInstance = require("./models/sql/instance"); + +let sessionStore; +let dbInstance; +let sessionStoreOption; +if (raccoonConfig.serverConfig.dbType === "mongodb") { + sessionStore = require("connect-mongo"); + dbInstance = require("mongoose"); + + sessionStoreOption = sessionStore.create({ + client: dbInstance.connection.getClient(), + dbName: raccoonConfig.dbConfig.dbName + }); + +} else if (raccoonConfig.serverConfig.dbType === "sql") { + sessionStore = require("connect-session-sequelize")(session.Store); + dbInstance = require("./models/sql/instance"); + + sessionStoreOption = new sessionStore({ + db: dbInstance + }); +} const passport = require("passport"); -const { DcmQrScp } = require('./dimse-sql'); +const { DcmQrScp } = require('@dimse'); require("dotenv"); require("./websocket"); @@ -48,6 +66,7 @@ app.use( //#region session + app.use( session({ secret: raccoonConfig.serverConfig.secretKey || "secretKey", @@ -57,9 +76,7 @@ app.use( httpOnly: true, maxAge: 60 * 60 * 1000 }, - store: new SequelizeStore({ - db: sequelizeInstance - }) + store: sessionStoreOption }) ); From b00b525364bee9688bc8f0fd01848431ab050a9d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 13:37:26 +0800 Subject: [PATCH 178/365] feat(api): move set patient into data adjust in create ups - Adds a parameter to the `findOneOrCreatePatient` method to specify the patient ID - Updates references to `findOneOrCreatePatient` to pass the patient ID as an argument - Updates the `validateWorkItem` method to set the patient ID in the DICOM JSON --- .../UPS-RS/service/create-workItem.service.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index ab552105..59ab0d19 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -27,7 +27,8 @@ class CreateWorkItemService extends BaseWorkItemService { await this.dataAdjustBeforeCreatingUps(uid); await this.validateWorkItem(uid); - let patient = await this.findOneOrCreatePatient(); + let patientId = this.requestWorkItem.getString("00100020"); + let patient = await this.findOneOrCreatePatient(patientId); let workItem = new workItemModel(this.requestWorkItem.dicomJson); let savedWorkItem = await workItem.save(); @@ -53,6 +54,9 @@ class CreateWorkItemService extends BaseWorkItemService { ] }); } + + let patientId = this.requestWorkItem.getString("00100020"); + _.set(this.requestWorkItem.dicomJson, "patientID", patientId); } async validateWorkItem(uid) { @@ -103,10 +107,7 @@ class CreateWorkItemService extends BaseWorkItemService { this.triggerUpsEvents(); } - async findOneOrCreatePatient() { - let patientId = this.requestWorkItem.getString("00100020"); - _.set(this.requestWorkItem.dicomJson, "patientID", patientId); - + async findOneOrCreatePatient(patientId) { /** @type {PatientModel | null} */ let patient = await PatientModel.findOne({ "00100020.Value": patientId From cab70ebc448f51397167475d440e85d990f8003e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 14:04:56 +0800 Subject: [PATCH 179/365] refactor: module alias for ups service --- api/dicom-web/controller/UPS-RS/create-workItems.js | 2 +- config/modula-alias/mongodb/package.json | 1 + config/modula-alias/sql/package.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/dicom-web/controller/UPS-RS/create-workItems.js b/api/dicom-web/controller/UPS-RS/create-workItems.js index e99a3f3c..78269bcd 100644 --- a/api/dicom-web/controller/UPS-RS/create-workItems.js +++ b/api/dicom-web/controller/UPS-RS/create-workItems.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { CreateWorkItemService -} = require("./service/create-workItem.service"); +} = require("@ups-service/create-workItem.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/config/modula-alias/mongodb/package.json b/config/modula-alias/mongodb/package.json index 6ab96a29..8a74ded6 100644 --- a/config/modula-alias/mongodb/package.json +++ b/config/modula-alias/mongodb/package.json @@ -18,6 +18,7 @@ "@delete-service": "../../../api/dicom-web/controller/WADO-RS/deletion/service/delete.js", "@rendered-service": "../../../api/dicom-web/controller/WADO-RS/service/rendered.service.js", "@thumbnail-service": "../../../api/dicom-web/controller/WADO-RS/service/thumbnail.service.js", + "@ups-service": "../../../api/dicom-web/controller/UPS-RS/service", "@dimse-query-builder": "../../../dimse/queryBuilder.js", "@dimse-patient-query-task": "../../../dimse/patientQueryTask.js", "@dimse-study-query-task": "../../../dimse/studyQueryTask.js", diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index 7aa0693e..e30f7f83 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -18,6 +18,7 @@ "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js", "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js", "@thumbnail-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js", + "@ups-service": "../../../api-sql/dicom-web/controller/UPS-RS/service", "@dimse-query-builder": "../../../dimse-sql/queryBuilder.js", "@dimse-patient-query-task": "../../../dimse-sql/patientQueryTask.js", "@dimse-study-query-task": "../../../dimse-sql/studyQueryTask.js", From a6ff6a56bc327600d7ee16e9c24074cc9b20e868 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 16:07:14 +0800 Subject: [PATCH 180/365] build(sql): add configuration for force sync table --- config-class.js | 1 + models/sql/init.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config-class.js b/config-class.js index fe7e2c94..c1de7812 100644 --- a/config-class.js +++ b/config-class.js @@ -33,6 +33,7 @@ class SqlDbConfig { this.username = env.get("SQL_USERNAME").default("postgres").asString(); this.password = env.get("SQL_PASSWORD").default("postgres").asString(); this.logging = env.get("SQL_LOGGING").default("false").asBool(); + this.forceSync = env.get("SQL_FORCE_SYNC").default("false").asBool(); this.dbName = this.database; } } diff --git a/models/sql/init.js b/models/sql/init.js index 36992be0..4c77557b 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -130,7 +130,9 @@ async function init() { }); //TODO: 設計完畢後要將 force 刪除 - await sequelizeInstance.sync({force: true}); + await sequelizeInstance.sync({ + force: raccoonConfig.dbConfig.forceSync + }); } catch (e) { console.error('Unable to connect to the database:', e); process.exit(1); From c517f41321dae0d78a18dd4e0048fafa65dfd4e4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 16:08:02 +0800 Subject: [PATCH 181/365] feat: add DT transform value for sql --- models/sql/po/utils.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 models/sql/po/utils.js diff --git a/models/sql/po/utils.js b/models/sql/po/utils.js new file mode 100644 index 00000000..0edd878a --- /dev/null +++ b/models/sql/po/utils.js @@ -0,0 +1,7 @@ +const moment = require("moment"); + +const vrValueTransform = { + "DT": (v) => v ? moment(v, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString(): undefined +}; + +module.exports.vrValueTransform = vrValueTransform; \ No newline at end of file From ed58e5484e3abcefc8b050505e2283ef15953642 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 19 Nov 2023 16:10:32 +0800 Subject: [PATCH 182/365] feat: add first version of creating work item service - The service extends the `CreateWorkItemService` class and includes methods for creating UPS (Unified Procedure Step) work items --- .../UPS-RS/service/create-workItem.service.js | 49 +++++++++ models/sql/init.js | 6 ++ models/sql/models/workitems.model.js | 24 ++--- models/sql/po/upsWorkItem.po.js | 100 ++++++++++++++++++ routes.js | 2 +- 5 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js create mode 100644 models/sql/po/upsWorkItem.po.js diff --git a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js new file mode 100644 index 00000000..378283d3 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -0,0 +1,49 @@ +const { PatientModel } = require("@dbModels/patient.model"); +const { UIDUtils } = require("@dcm4che/util/UIDUtils"); +const { WorkItemModel } = require("@models/sql/models/workItems.model"); +const { CreateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/create-workItem.service"); +const { get, set } = require("lodash"); +const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); +const { PatientPersistentObject } = require("@models/sql/po/patient.po"); + +class SqlCreateWorkItemService extends CreateWorkItemService { + constructor(req, res) { + super(req, res); + } + + async createUps() { + let uid = get(this.request, "query.workitem", + await UIDUtils.createUID() + ); + await this.dataAdjustBeforeCreatingUps(uid); + await this.validateWorkItem(uid); + + let patientId = this.requestWorkItem.getString("00100020"); + let patient = await this.findOneOrCreatePatient(patientId); + let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); + let savedWorkItem = await workItem.save(); + + //TODO: subscription + //this.triggerCreateEvent(savedWorkItem); + + return workItem; + } + + async findOneOrCreatePatient(patientId) { + /** @type {PatientModel | null} */ + let patientPersistent = new PatientPersistentObject(this.requestWorkItem.dicomJson); + let patient = await patientPersistent.createPatient(); + + return patient; + } + + async isUpsExist(uid) { + return await WorkItemModel.findOne({ + where: { + upsInstanceUID: uid + } + }); + } +} + +module.exports.CreateWorkItemService = SqlCreateWorkItemService; \ No newline at end of file diff --git a/models/sql/init.js b/models/sql/init.js index 4c77557b..9b53eaf2 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -11,6 +11,7 @@ const { SeriesRequestAttributesModel } = require("./models/seriesRequestAttribut const { DicomCodeModel } = require("./models/dicomCode.model"); const { DicomContentSqModel } = require("./models/dicomContentSQ.model"); const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model"); +const { WorkItemModel } = require("./models/workItems.model"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -128,6 +129,11 @@ async function init() { DicomContentSqModel.hasOne(DicomCodeModel, { as: "ConceptCode" }); + + WorkItemModel.belongsTo(PatientModel, { + foreignKey: "x00100020", + targetKey: "x00100020" + }); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({ diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index 2dcaf5e8..b1358fbb 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -20,7 +20,8 @@ class WorkItemModel extends Model { } }; -WorkItemModel.init({ +/** @type { import("sequelize").ModelAttributes } */ +const WorkItemSchema = { upsInstanceUID: { type: DataTypes.STRING, allowNull: false @@ -33,21 +34,13 @@ WorkItemModel.init({ type: DataTypes.STRING }, subscribed: { - type: DataTypes.INTEGER, - default: SUBSCRIPTION_STATE.NOT_SUBSCRIBED + type: DataTypes.INTEGER, + defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED }, //#region patient level - "x00100010": { - type: DataTypes.INTEGER - }, "x00100020": { - type: vrTypeMapping.LO - }, - "x00100021": { - type: vrTypeMapping.LO - }, - "x00100040": { - type: vrTypeMapping.CS + type: vrTypeMapping.LO, + allowNull: false }, //#endregion "x00080018": { @@ -99,7 +92,9 @@ WorkItemModel.init({ "json": { type: vrTypeMapping.JSON } -}, { +}; + +WorkItemModel.init(WorkItemSchema, { sequelize: sequelizeInstance, modelName: "UPSWorkItem", tableName: "UPSWorkItem", @@ -107,3 +102,4 @@ WorkItemModel.init({ }); module.exports.WorkItemModel = WorkItemModel; +module.exports.WorkItemSchema = WorkItemSchema; diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js new file mode 100644 index 00000000..c99b5e31 --- /dev/null +++ b/models/sql/po/upsWorkItem.po.js @@ -0,0 +1,100 @@ +const moment = require("moment"); +const { get, set } = require("lodash"); +const { PersonNameModel } = require("../models/personName.model"); +const { StudyModel } = require("../models/study.model"); +const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { vrValueTransform } = require("./utils"); +const { WorkItemModel } = require("../models/workItems.model"); + + +class UpsWorkItemPersistentObject { + constructor(dicomJson, patient) { + + this.json = {}; + this.initJsonProperties(dicomJson); + + this.patient = patient; + + let dicomJsonObj = new BaseDicomJson(dicomJson); + this.upsInstanceUID = get(dicomJson, "upsInstanceUID", ""); + this.patientID = get(dicomJson, "patientID", ""); + this.transactionUID = get(dicomJson, "transactionUID", ""); + + this.x00100020 = dicomJsonObj.getValue("00100020"); + this.x00080018 = dicomJsonObj.getValue("00080018"); + this.x00741200 = dicomJsonObj.getValue("00741200"); + this.x00404010 = dicomJsonObj.getValue("00404010"); + this.x00741204 = dicomJsonObj.getValue("00741204"); + this.x00741202 = dicomJsonObj.getValue("00741202"); + this.x00404025 = dicomJsonObj.getValue("00404025"); + this.x00404026 = dicomJsonObj.getValue("00404026"); + this.x00404027 = dicomJsonObj.getValue("00404027"); + this.x00404034 = dicomJsonObj.getValue("00404034"); + this.x00404005 = dicomJsonObj.getValue("00404005"); + this.x00404011 = dicomJsonObj.getValue("00404011"); + this.x00380010 = dicomJsonObj.getValue("00380010"); + this.x00741000 = dicomJsonObj.getValue("00741000"); + this.x00080082 = dicomJsonObj.getValue("00080082"); + } + + initJsonProperties(dicomJson) { + Object.keys({ + ...tagsNeedStore.UPS, + ...tagsNeedStore.Patient + }).forEach(key => { + let value = get(dicomJson, key); + value ? set(this.json, key, value) : undefined; + }); + } + + async save() { + let item = { + json: this.json, + upsInstanceUID : this.upsInstanceUID, + patientID : this.patientID, + transactionUID : this.transactionUID, + x00100020: this.x00100020, + x00080018: this.x00080018, + x00741200: this.x00741200, + x00404010: vrValueTransform.DT(this.x00404010), + x00741204: this.x00741204, + x00741202: this.x00741202, + //TODO: dicom code x00404025: this.x00404025, + //TODO: dicom code x00404026: this.x00404026, + //TODO: dicom code x00404027: this.x00404027, + //TODO: dicom code x00404034: this.x00404034, + x00404005: vrValueTransform.DT(this.x00404005), + x00404011: vrValueTransform.DT(this.x00404011), + x00380010: this.x00380010, + x00741000: this.x00741000 + //TODO dicom code x00080082: this.x00080082 + }; + + let upsWorkItemObj = WorkItemModel.build(item); + let [upsWorkItem, created] = await WorkItemModel.findOrCreate({ + where: { + upsInstanceUID: this.upsInstanceUID + }, + defaults: upsWorkItemObj.toJSON() + }); + + if (created) { + let patientName = await PersonNameModel.createPersonName(this.x00100010); + upsWorkItem.x00100010 = patientName ? patientName.id : undefined; + await upsWorkItem.save(); + } else { + await WorkItemModel.update(item, { + where: { + upsInstanceUID: upsWorkItem.dataValues.upsInstanceUID + } + }); + await PersonNameModel.updatePersonNameById(this.x00100010, upsWorkItem.getDataValue("x00100010")); + } + + return upsWorkItem; + } +} + +module.exports.UpsWorkItemPersistentObject = UpsWorkItemPersistentObject; \ No newline at end of file diff --git a/routes.js b/routes.js index ee4626ba..a994920c 100644 --- a/routes.js +++ b/routes.js @@ -27,7 +27,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/wado-rs-bulkdata.route")); app.use("/dicom-web", require("./api/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); - // app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); + app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); app.use("/wado", require("./api/WADO-URI")); }; From bdc1ebef267b3ebd21b8f62e8f5af49dfa89e4cc Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Tue, 21 Nov 2023 08:09:30 +0800 Subject: [PATCH 183/365] fix: module alias name not consist Fix module alias name inconsistency This commit fixes an inconsistency in the module alias name "@query-dicom-json-factory" in the package.json file. The previous alias name was incorrect, and this change corrects it to the proper name. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2733257d..20a14d0e 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@dbModels": "./models/mongodb/models", "@dbInitializer": "./models/mongodb/index.js", "@dicom-json-model": "./models/DICOM/dicom-json-model.js", - "@query-json-factory": "./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js" + "@query-dicom-json-factory": "./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js" }, "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", From 794dbfa603be18abd794407dac52c4dd48747836 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 24 Nov 2023 23:22:22 +0800 Subject: [PATCH 184/365] refactor: remove useless code --- .../controller/UPS-RS/service/create-workItem.service.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 378283d3..8c5e21d7 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -18,8 +18,7 @@ class SqlCreateWorkItemService extends CreateWorkItemService { await this.dataAdjustBeforeCreatingUps(uid); await this.validateWorkItem(uid); - let patientId = this.requestWorkItem.getString("00100020"); - let patient = await this.findOneOrCreatePatient(patientId); + let patient = await this.findOneOrCreatePatient(); let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); let savedWorkItem = await workItem.save(); @@ -29,7 +28,7 @@ class SqlCreateWorkItemService extends CreateWorkItemService { return workItem; } - async findOneOrCreatePatient(patientId) { + async findOneOrCreatePatient() { /** @type {PatientModel | null} */ let patientPersistent = new PatientPersistentObject(this.requestWorkItem.dicomJson); let patient = await patientPersistent.createPatient(); From 4cb8ecd33f57654348a74e84f0a2e90f9e6daa14 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 24 Nov 2023 23:23:22 +0800 Subject: [PATCH 185/365] feat: store dicom code items in UPS work item --- models/sql/init.js | 31 +++++++++++++++++++++ models/sql/models/workitems.model.js | 28 ++++++++++++++----- models/sql/po/upsWorkItem.po.js | 40 +++++++++++++++++++--------- 3 files changed, 80 insertions(+), 19 deletions(-) diff --git a/models/sql/init.js b/models/sql/init.js index 9b53eaf2..6154db39 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -12,6 +12,7 @@ const { DicomCodeModel } = require("./models/dicomCode.model"); const { DicomContentSqModel } = require("./models/dicomContentSQ.model"); const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model"); const { WorkItemModel } = require("./models/workItems.model"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -134,6 +135,36 @@ async function init() { foreignKey: "x00100020", targetKey: "x00100020" }); + WorkItemModel.belongsToMany(DicomCodeModel, { + through: `rel_${dictionary.tag["00404025"]}`, + sourceKey: "upsInstanceUID", + foreignKey: "upsInstanceUID", + as: dictionary.tag["00404025"] + }); + WorkItemModel.belongsToMany(DicomCodeModel, { + through: `rel_${dictionary.tag["00404026"]}`, + sourceKey: "upsInstanceUID", + foreignKey: "upsInstanceUID", + as: dictionary.tag["00404026"] + }); + WorkItemModel.belongsToMany(DicomCodeModel, { + through: `rel_${dictionary.tag["00404027"]}`, + sourceKey: "upsInstanceUID", + foreignKey: "upsInstanceUID", + as: dictionary.tag["00404027"] + }); + WorkItemModel.belongsToMany(DicomCodeModel, { + through: `rel_${dictionary.tag["00404009"]}`, + sourceKey: "upsInstanceUID", + foreignKey: "upsInstanceUID", + as: dictionary.tag["00404009"] + }); + WorkItemModel.belongsToMany(DicomCodeModel, { + through: `rel_${dictionary.tag["00404018"]}`, + sourceKey: "id", + foreignKey: "upsInstanceUID", + as: dictionary.tag["00404018"] + }); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({ diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index b1358fbb..9aacaedb 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -43,9 +43,6 @@ const WorkItemSchema = { allowNull: false }, //#endregion - "x00080018": { - type: vrTypeMapping.UI - }, "x00741200": { type: vrTypeMapping.CS }, @@ -59,19 +56,19 @@ const WorkItemSchema = { type: vrTypeMapping.LO }, "x00404025": { - // TODO: DICOM Code Model, and do reference + // DICOM Code type: DataTypes.INTEGER }, "x00404026": { - // TODO: DICOM Code Model, and do reference + // DICOM Code type: DataTypes.INTEGER }, "x00404027": { - // TODO: DICOM Code Model, and do reference + // DICOM Code type: DataTypes.INTEGER }, "x00404034": { - // TODO: DICOM Code Model, and do reference + // DICOM Code type: DataTypes.INTEGER }, "x00404005": { @@ -80,6 +77,10 @@ const WorkItemSchema = { "x00404011": { type: vrTypeMapping.DT }, + "x00404018": { + // DICOM Code + type: DataTypes.INTEGER + }, "x00380010": { type: vrTypeMapping.LO }, @@ -89,6 +90,19 @@ const WorkItemSchema = { "x00080082": { type: DataTypes.INTEGER }, + // #region Scheduled Human Performers Sequence + "x00404009": { + // DICOM Code + type: DataTypes.INTEGER + }, + "x00404037": { + // Person Name + type: DataTypes.INTEGER + }, + "x00404036": { + type: vrTypeMapping.LO + }, + // #endregion "json": { type: vrTypeMapping.JSON } diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index c99b5e31..b96670e6 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -1,12 +1,11 @@ -const moment = require("moment"); const { get, set } = require("lodash"); const { PersonNameModel } = require("../models/personName.model"); -const { StudyModel } = require("../models/study.model"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { vrValueTransform } = require("./utils"); const { WorkItemModel } = require("../models/workItems.model"); +const { DicomCodeModel } = require("../models/dicomCode.model"); class UpsWorkItemPersistentObject { @@ -31,9 +30,12 @@ class UpsWorkItemPersistentObject { this.x00404025 = dicomJsonObj.getValue("00404025"); this.x00404026 = dicomJsonObj.getValue("00404026"); this.x00404027 = dicomJsonObj.getValue("00404027"); - this.x00404034 = dicomJsonObj.getValue("00404034"); this.x00404005 = dicomJsonObj.getValue("00404005"); + this.x00404009 = dicomJsonObj.getValue("00404009"); + this.x00404037 = dicomJsonObj.getValue("00404037"); + this.x00404036 = dicomJsonObj.getValue("00404036"); this.x00404011 = dicomJsonObj.getValue("00404011"); + this.x00400418 = dicomJsonObj.getValue("00400418"); this.x00380010 = dicomJsonObj.getValue("00380010"); this.x00741000 = dicomJsonObj.getValue("00741000"); this.x00080082 = dicomJsonObj.getValue("00080082"); @@ -56,20 +58,15 @@ class UpsWorkItemPersistentObject { patientID : this.patientID, transactionUID : this.transactionUID, x00100020: this.x00100020, - x00080018: this.x00080018, x00741200: this.x00741200, x00404010: vrValueTransform.DT(this.x00404010), x00741204: this.x00741204, x00741202: this.x00741202, - //TODO: dicom code x00404025: this.x00404025, - //TODO: dicom code x00404026: this.x00404026, - //TODO: dicom code x00404027: this.x00404027, - //TODO: dicom code x00404034: this.x00404034, + x00404009: this.x00404009, x00404005: vrValueTransform.DT(this.x00404005), x00404011: vrValueTransform.DT(this.x00404011), x00380010: this.x00380010, x00741000: this.x00741000 - //TODO dicom code x00080082: this.x00080082 }; let upsWorkItemObj = WorkItemModel.build(item); @@ -79,10 +76,16 @@ class UpsWorkItemPersistentObject { }, defaults: upsWorkItemObj.toJSON() }); + await upsWorkItem.setPatient(this.patient); + await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationNameCodeSequence); + await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationClassCodeSequence); + await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationGeographicLocationCodeSequence); + await this.setGeneralCode(upsWorkItem, dictionary.keyword.HumanPerformerCodeSequence); + await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledWorkitemCodeSequence); + await this.setGeneralCode(upsWorkItem, dictionary.keyword.InstitutionCodeSequence); + if (created) { - let patientName = await PersonNameModel.createPersonName(this.x00100010); - upsWorkItem.x00100010 = patientName ? patientName.id : undefined; await upsWorkItem.save(); } else { await WorkItemModel.update(item, { @@ -90,11 +93,24 @@ class UpsWorkItemPersistentObject { upsInstanceUID: upsWorkItem.dataValues.upsInstanceUID } }); - await PersonNameModel.updatePersonNameById(this.x00100010, upsWorkItem.getDataValue("x00100010")); } return upsWorkItem; } + + + async setGeneralCode(item, tag) { + if (this[`x${tag}`]) { + let code = await DicomCodeModel.create({ + "x00080100": get(this[`x${tag}`], "00080100.Value.0", undefined), + "x00080102": get(this[`x${tag}`], "00080102.Value.0", undefined), + "x00080103": get(this[`x${tag}`], "00080103.Value.0", undefined), + "x00080104": get(this[`x${tag}`], "00080104.Value.0", undefined) + }); + let keyword = dictionary.tag[tag]; + await item[`set${keyword}`](code); + } + } } module.exports.UpsWorkItemPersistentObject = UpsWorkItemPersistentObject; \ No newline at end of file From 2810f96cf148578c77dc7746167f1ac743454ce2 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 25 Nov 2023 00:07:24 +0800 Subject: [PATCH 186/365] feat: add `Human Performer's Name` for UPS model --- models/sql/init.js | 7 ++++++- models/sql/models/workitems.model.js | 7 ++----- models/sql/po/upsWorkItem.po.js | 18 +++++++++++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/models/sql/init.js b/models/sql/init.js index 6154db39..dd38ac13 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -161,10 +161,15 @@ async function init() { }); WorkItemModel.belongsToMany(DicomCodeModel, { through: `rel_${dictionary.tag["00404018"]}`, - sourceKey: "id", + sourceKey: "upsInstanceUID", foreignKey: "upsInstanceUID", as: dictionary.tag["00404018"] }); + WorkItemModel.belongsToMany(PersonNameModel, { + through: "rel_human_performer_s_name", + sourceKey: "upsInstanceUID", + as: dictionary.tag["00404037"] + }); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({ diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index 9aacaedb..a909de1b 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -24,7 +24,8 @@ class WorkItemModel extends Model { const WorkItemSchema = { upsInstanceUID: { type: DataTypes.STRING, - allowNull: false + allowNull: false, + unique: true }, patientID: { type: DataTypes.STRING, @@ -95,10 +96,6 @@ const WorkItemSchema = { // DICOM Code type: DataTypes.INTEGER }, - "x00404037": { - // Person Name - type: DataTypes.INTEGER - }, "x00404036": { type: vrTypeMapping.LO }, diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index b96670e6..2d26317d 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -31,9 +31,10 @@ class UpsWorkItemPersistentObject { this.x00404026 = dicomJsonObj.getValue("00404026"); this.x00404027 = dicomJsonObj.getValue("00404027"); this.x00404005 = dicomJsonObj.getValue("00404005"); - this.x00404009 = dicomJsonObj.getValue("00404009"); - this.x00404037 = dicomJsonObj.getValue("00404037"); - this.x00404036 = dicomJsonObj.getValue("00404036"); + let scheduledHumanPerformerSequence = new BaseDicomJson(dicomJsonObj.getValue("00404034")); + this.x00404009 = scheduledHumanPerformerSequence.getValue("00404009"); + this.x00404037 = scheduledHumanPerformerSequence.getValue("00404037"); + this.x00404036 = scheduledHumanPerformerSequence.getValue("00404036"); this.x00404011 = dicomJsonObj.getValue("00404011"); this.x00400418 = dicomJsonObj.getValue("00400418"); this.x00380010 = dicomJsonObj.getValue("00380010"); @@ -62,7 +63,7 @@ class UpsWorkItemPersistentObject { x00404010: vrValueTransform.DT(this.x00404010), x00741204: this.x00741204, x00741202: this.x00741202, - x00404009: this.x00404009, + x00404036: this.x00404036, x00404005: vrValueTransform.DT(this.x00404005), x00404011: vrValueTransform.DT(this.x00404011), x00380010: this.x00380010, @@ -83,6 +84,7 @@ class UpsWorkItemPersistentObject { await this.setGeneralCode(upsWorkItem, dictionary.keyword.HumanPerformerCodeSequence); await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledWorkitemCodeSequence); await this.setGeneralCode(upsWorkItem, dictionary.keyword.InstitutionCodeSequence); + await this.setHumanPerformerName(upsWorkItem); if (created) { @@ -98,7 +100,6 @@ class UpsWorkItemPersistentObject { return upsWorkItem; } - async setGeneralCode(item, tag) { if (this[`x${tag}`]) { let code = await DicomCodeModel.create({ @@ -111,6 +112,13 @@ class UpsWorkItemPersistentObject { await item[`set${keyword}`](code); } } + + async setHumanPerformerName(upsWorkItem) { + if (this.x00404037) { + let nameOfHumanPerformer = await PersonNameModel.createPersonName(this.x00404037); + await upsWorkItem[`set${dictionary.tag["00404037"]}`](nameOfHumanPerformer); + } + } } module.exports.UpsWorkItemPersistentObject = UpsWorkItemPersistentObject; \ No newline at end of file From 2624db4410014601204414f9c71abef401b594ad Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 25 Nov 2023 10:07:39 +0800 Subject: [PATCH 187/365] build: add `SQL_LOGGING` and `SQL_FORCE_SYNC` in .env --- .env.template | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.template b/.env.template index 42da61fc..2019778c 100644 --- a/.env.template +++ b/.env.template @@ -5,6 +5,8 @@ SQL_DB="raccoon" SQL_TYPE="postgres" SQL_USERNAME="postgres" SQL_PASSWORD="postgres" +SQL_LOGGING=false +SQL_FORCE_SYNC=false # Server SERVER_PORT=8081 From 1772da8ca95fb90db66142ddd1081e041aa04c5c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 25 Nov 2023 12:57:03 +0800 Subject: [PATCH 188/365] feat(UPS-SQL): Update code association to use 'belongsTo' This change updates the code association in the UPS-SQL model to utilize the 'belongsTo' association instead of 'belongsToMany'. This modification provides a more accurate representation of the relationship between the WorkItemModel and DicomCodeModel, as it signifies that a WorkItem belongs to a single DicomCode. --- models/sql/init.js | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/models/sql/init.js b/models/sql/init.js index dd38ac13..f12398a0 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -135,39 +135,30 @@ async function init() { foreignKey: "x00100020", targetKey: "x00100020" }); - WorkItemModel.belongsToMany(DicomCodeModel, { - through: `rel_${dictionary.tag["00404025"]}`, - sourceKey: "upsInstanceUID", - foreignKey: "upsInstanceUID", - as: dictionary.tag["00404025"] - }); - WorkItemModel.belongsToMany(DicomCodeModel, { - through: `rel_${dictionary.tag["00404026"]}`, - sourceKey: "upsInstanceUID", - foreignKey: "upsInstanceUID", + + WorkItemModel.belongsTo(DicomCodeModel, { + as: dictionary.tag["00404025"], + foreignKey: "x00404025" + }); + WorkItemModel.belongsTo(DicomCodeModel, { + foreignKey: "x00404026", as: dictionary.tag["00404026"] }); - WorkItemModel.belongsToMany(DicomCodeModel, { - through: `rel_${dictionary.tag["00404027"]}`, - sourceKey: "upsInstanceUID", - foreignKey: "upsInstanceUID", + WorkItemModel.belongsTo(DicomCodeModel, { + foreignKey: "x00404027", as: dictionary.tag["00404027"] }); - WorkItemModel.belongsToMany(DicomCodeModel, { - through: `rel_${dictionary.tag["00404009"]}`, - sourceKey: "upsInstanceUID", - foreignKey: "upsInstanceUID", + WorkItemModel.belongsTo(DicomCodeModel, { + foreignKey: "x00404009", as: dictionary.tag["00404009"] }); - WorkItemModel.belongsToMany(DicomCodeModel, { - through: `rel_${dictionary.tag["00404018"]}`, - sourceKey: "upsInstanceUID", - foreignKey: "upsInstanceUID", + WorkItemModel.belongsTo(DicomCodeModel, { + foreignKey: "x00404018", as: dictionary.tag["00404018"] }); - WorkItemModel.belongsToMany(PersonNameModel, { - through: "rel_human_performer_s_name", - sourceKey: "upsInstanceUID", + + WorkItemModel.belongsTo(PersonNameModel, { + foreignKey: "x00404037", as: dictionary.tag["00404037"] }); From e71a032bc5a517aaa09f86299558cc181669bdb6 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 25 Nov 2023 17:04:11 +0800 Subject: [PATCH 189/365] feat: add columns for attributes in issuer of Admission ID Sequence --- models/sql/models/workitems.model.js | 12 ++++++++++++ models/sql/po/upsWorkItem.po.js | 11 ++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index a909de1b..252f370f 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -85,6 +85,15 @@ const WorkItemSchema = { "x00380010": { type: vrTypeMapping.LO }, + "x00380014_x00400031": { + type: vrTypeMapping.UT + }, + "x00380014_x00400032": { + type: vrTypeMapping.UT + }, + "x00380014_x00400033": { + type: vrTypeMapping.CS + }, "x00741000": { type: vrTypeMapping.CS }, @@ -103,6 +112,9 @@ const WorkItemSchema = { "json": { type: vrTypeMapping.JSON } + //TODO: Referenced Request Sequence + // You should create new Model for Referenced Request Sequence (0040,A370) + // model name should be called UPSRequest }; WorkItemModel.init(WorkItemSchema, { diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index 2d26317d..ae6d9540 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -40,6 +40,12 @@ class UpsWorkItemPersistentObject { this.x00380010 = dicomJsonObj.getValue("00380010"); this.x00741000 = dicomJsonObj.getValue("00741000"); this.x00080082 = dicomJsonObj.getValue("00080082"); + + // issuer of Admission ID + let issuerOfAdmissionIdSequence = new BaseDicomJson(dicomJsonObj.getValue("00380014")); + this.admissionLocalEntityId = issuerOfAdmissionIdSequence.getValue("00400031"); + this.admissionUniversalEntityId = issuerOfAdmissionIdSequence.getValue("00400032"); + this.admissionUniversalEntityIdType = issuerOfAdmissionIdSequence.getValue("00400033"); } initJsonProperties(dicomJson) { @@ -67,7 +73,10 @@ class UpsWorkItemPersistentObject { x00404005: vrValueTransform.DT(this.x00404005), x00404011: vrValueTransform.DT(this.x00404011), x00380010: this.x00380010, - x00741000: this.x00741000 + x00741000: this.x00741000, + x00380014_x00400031: this.admissionLocalEntityId, + x00380014_x00400032: this.admissionUniversalEntityId, + x00380014_x00400033: this.admissionUniversalEntityIdType }; let upsWorkItemObj = WorkItemModel.build(item); From 13dcd246e1c9ac9ca8b75464ef6ae645964fe182 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 25 Nov 2023 17:25:52 +0800 Subject: [PATCH 190/365] feat: Implement UPS query for getting work items --- .../QIDO-RS/service/dicomCodeQueryBuilder.js | 90 +++++++ .../UPS-RS/service/get-workItem.service.js | 27 +++ .../UPS-RS/service/query/upsQueryBuilder.js | 221 ++++++++++++++++++ .../controller/UPS-RS/get-workItem.js | 2 +- config/jsconfig.sql.json | 1 + models/sql/dicom-json-model.js | 3 +- models/sql/models/workitems.model.js | 18 ++ 7 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js new file mode 100644 index 00000000..6f03d413 --- /dev/null +++ b/api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js @@ -0,0 +1,90 @@ +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { BaseQueryBuilder } = require("./querybuilder"); +const { DicomCodeModel } = require("@models/sql/models/dicomCode.model"); +const { Op } = require("sequelize"); + +class DicomCodeQueryBuilder { + constructor(queryBuilder, codeTableName) { + /** @type { import("../../UPS-RS/service/query/upsQueryBuilder") } */ + this.queryBuilder = queryBuilder; + this.codeTableName = codeTableName; + } + + isModelIncluded() { + this.queryBuilder.includeQueries.forEach(v=> console.log(v.model.getTableName())); + return this.queryBuilder.includeQueries.find(v => v.model.getTableName() === this.codeTableName || v.as === this.codeTableName); + } + + /** + * + * @param {string[]} values + */ + getCodeValue(values) { + let q = this.queryBuilder.getOrQuery( + dictionary.keyword.CodeValue, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.queryBuilder) + ); + this.addQuery(q); + } + + /** + * + * @param {string[]} values + */ + getCodingSchemeDesignator(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodingSchemeDesignator, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); + this.addQuery(q); + } + + /** + * + * @param {string[]} values + */ + getCodingSchemeVersion(values) { + let q = this.instanceQueryBuilder.getOrQuery( + dictionary.keyword.CodingSchemeVersion, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); + this.addQuery(q); + } + + /** + * + * @param {string[]} values + */ + getCodeMeaning(values) { + let q = this.getOrQuery( + dictionary.keyword.CodeMeaning, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) + ); + this.addQuery(q); + } + + addQuery(q) { + let currentCodeModel = this.isModelIncluded(); + if (currentCodeModel) { + currentCodeModel.where = { + ...currentCodeModel.where, + ...q + }; + } else { + this.queryBuilder.includeQueries.push({ + model: DicomCodeModel, + where: { + ...q + }, + as: this.codeTableName, + attributes: [] + }); + } + } +} + +module.exports.DicomCodeQueryBuilder = DicomCodeQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js new file mode 100644 index 00000000..1e5f0da6 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -0,0 +1,27 @@ +const _ = require("lodash"); +const { GetWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/get-workItem.service"); +const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { WorkItemModel } = require("@models/sql/models/workItems.model"); + +class SqlGetWorkItemService extends GetWorkItemService { + constructor(req, res) { + super(req, res); + } + + async getUps() { + let queryOptions = { + query: this.query, + skip: this.skip_, + limit: this.limit_, + requestParams: this.request.params + }; + + let docs = await WorkItemModel.getDicomJson(queryOptions); + + return this.adjustDocs(docs); + } +} + +SqlGetWorkItemService.prototype.initQuery_ = QidoRsService.prototype.initQuery_; + +module.exports.GetWorkItemService = SqlGetWorkItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js b/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js new file mode 100644 index 00000000..94168cda --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js @@ -0,0 +1,221 @@ +const sequelize = require("@models/sql/instance"); +const { PatientQueryBuilder } = require("../../../QIDO-RS/service/patientQueryBuilder"); +const { BaseQueryBuilder } = require("../../../QIDO-RS/service/querybuilder"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { DicomCodeQueryBuilder } = require("../../../QIDO-RS/service/dicomCodeQueryBuilder"); + +class UpsQueryBuilder extends BaseQueryBuilder { + + constructor(queryOptions) { + super(queryOptions); + + let patientQueryBuilder = new PatientQueryBuilder(queryOptions); + let patientQuery = patientQueryBuilder.build(); + this.includeQueries.push({ + model: sequelize.model("Patient"), + attributes: ["x00100020"], + ...patientQuery, + required: true + }); + + this.createCodeQueries(dictionary.keyword.ScheduledStationNameCodeSequence); + this.createCodeQueries(dictionary.keyword.ScheduledStationClassCodeSequence); + this.createCodeQueries(dictionary.keyword.ScheduledStationGeographicLocationCodeSequence); + this.createCodeQueries(dictionary.keyword.HumanPerformerCodeSequence); + this.createCodeQueries(dictionary.keyword.ScheduledWorkitemCodeSequence); + this.createScheduledHumanPerformersSequenceQueries(); + this.createIssuerOfAdmissionIdSequenceQueries(); + } + + /** + * + * @param {string[]} values + * @returns + */ + getSOPInstanceUID(values) { + return this.getOrQuery(dictionary.keyword.SOPInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); + } + + /** + * + * @param {string[]} values + * @returns + */ + getScheduledProcedureStepPriority(values) { + return this.getOrQuery( + dictionary.keyword.ScheduledProcedureStepPriority, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + /** + * + * @param {string[]} values + * @returns + */ + getScheduledProcedureStepModificationDateTime(values) { + return this.getOrQuery( + dictionary.keyword.ScheduledProcedureStepModificationDateTime, + values, + BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) + ); + } + + /** + * + * @param {string[]} values + * @returns + */ + getProcedureStepLabel(values) { + return this.getOrQuery(dictionary.keyword.ProcedureStepLabel, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); + } + + /** + * + * @param {string[]} values + * @returns + */ + getWorklistLabel(values) { + return this.getOrQuery(dictionary.keyword.WorklistLabel, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); + } + + /** + * + * @param {string[]} values + * @returns + */ + getScheduledProcedureStepStartDateTime(values) { + return this.getOrQuery( + dictionary.keyword.ScheduledProcedureStepStartDateTime, + values, + BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) + ); + } + + /** + * + * @param {string[]} values + * @returns + */ + getExpectedCompletionDateTime(values) { + return this.getOrQuery( + dictionary.keyword.ExpectedCompletionDateTime, + values, + BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) + ); + } + + /** + * + * @param {string[]} values + * @returns + */ + getScheduledProcedureStepExpirationDateTime(values) { + return this.getOrQuery( + dictionary.keyword.ScheduledProcedureStepExpirationDateTime, + values, + BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) + ); + } + + /** + * + * @param {string[]} values + * @returns + */ + getInputReadinessState(values) { + return this.getOrQuery( + dictionary.keyword.InputReadinessState, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + /** + * + * @param {string[]} values + * @returns + */ + getAdmissionID(values) { + return this.getOrQuery( + dictionary.keyword.AdmissionID, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + /** + * + * @param {string[]} values + * @returns + */ + getProcedureStepState(values) { + return this.getOrQuery( + dictionary.keyword.ProcedureStepState, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + createCodeQueries(tag) { + let dicomCodeQueryBuilder = new DicomCodeQueryBuilder(this, dictionary.tag[tag]); + this[`${tag}.00080100`] = (values) => dicomCodeQueryBuilder.getCodeValue(values); + this[`${tag}.00080102`] = (values) => dicomCodeQueryBuilder.getCodingSchemeDesignator(values); + this[`${tag}.00080103`] = (values) => dicomCodeQueryBuilder.getCodingSchemeVersion(values); + this[`${tag}.00080104`] = (values) => dicomCodeQueryBuilder.getCodeMeaning(values); + } + + createIssuerOfAdmissionIdSequenceQueries() { + this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.LocalNamespaceEntityID}`] = (values) => { + return this.getOrQuery( + `${dictionary.keyword.IssuerOfAdmissionIDSequence}_x${dictionary.keyword.LocalNamespaceEntityID}`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityID}`] = (values) => { + return this.getOrQuery( + `${dictionary.keyword.IssuerOfAdmissionIDSequence}_x${dictionary.keyword.UniversalEntityID}`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityIDType}`] = (values) => { + return this.getOrQuery( + `${dictionary.keyword.IssuerOfAdmissionIDSequence}_x${dictionary.keyword.UniversalEntityIDType}`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + } + + createScheduledHumanPerformersSequenceQueries() { + this[`${dictionary.keyword.ScheduledHumanPerformersSequence}.${dictionary.keyword.HumanPerformerName}`] = (values) => { + let q = this.getOrQuery( + `${dictionary.keyword.ScheduledHumanPerformersSequence}.${dictionary.keyword.HumanPerformerName}`, + values, + BaseQueryBuilder.prototype.getPersonNameQuery.bind(this) + ); + this.includeQueries.push({ + model: sequelize.model("PersonName"), + as: dictionary.tag["00404037"], + where: { + ...q + }, + attributes: [] + }); + }; + this[`${dictionary.keyword.ScheduledHumanPerformersSequence}.${dictionary.keyword.HumanPerformerOrganization}`] = (values) => { + return this.getOrQuery( + dictionary.keyword.HumanPerformerOrganization, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + } +} + +module.exports.UpsQueryBuilder = UpsQueryBuilder; \ No newline at end of file diff --git a/api/dicom-web/controller/UPS-RS/get-workItem.js b/api/dicom-web/controller/UPS-RS/get-workItem.js index 17e66fde..133e0c6f 100644 --- a/api/dicom-web/controller/UPS-RS/get-workItem.js +++ b/api/dicom-web/controller/UPS-RS/get-workItem.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { GetWorkItemService -} = require("./service/get-workItem.service"); +} = require("@ups-service/get-workItem.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index e5a230d8..df2bc8ca 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -19,6 +19,7 @@ "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"], "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"], "@thumbnail-service": ["./api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], + "@ups-service/*": ["./api-sql/dicom-web/controller/UPS-RS/service/*"], "@dimse-query-builder": ["./dimse-sql/queryBuilder.js"], "@dimse-patient-query-task": ["./dimse-sql/patientQueryTask.js"], "@dimse-study-query-task": ["./dimse-sql/studyQueryTask.js"], diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js index f8c31f25..4fd110ea 100644 --- a/models/sql/dicom-json-model.js +++ b/models/sql/dicom-json-model.js @@ -4,7 +4,7 @@ const fsP = require("fs/promises"); const path = require("path"); const mkdirp = require("mkdirp"); -const { DicomJsonModel, DicomJsonBinaryDataModel } = require("@models/DICOM/dicom-json-model"); +const { BaseDicomJson, DicomJsonModel, DicomJsonBinaryDataModel } = require("@models/DICOM/dicom-json-model"); const { PatientPersistentObject } = require("./po/patient.po"); const { StudyPersistentObject } = require("./po/study.po"); const { SeriesPersistentObject } = require("./po/series.po"); @@ -90,4 +90,5 @@ class BulkData { } module.exports.DicomJsonModel = DicomJsonModel; +module.exports.BaseDicomJson = BaseDicomJson; module.exports.DicomJsonBinaryDataModel = SqlDicomJsonBinaryDataModel; \ No newline at end of file diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index 252f370f..fd800623 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -3,6 +3,7 @@ const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { raccoonConfig } = require("@root/config-class"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); +const { UpsQueryBuilder } = require("@root/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder"); let Common; if (raccoonConfig.dicomDimseConfig.enableDimse) { @@ -18,6 +19,23 @@ class WorkItemModel extends Model { let jsonStr = JSON.stringify(obj.json); return await Common.getAttributesFromJsonString(jsonStr); } + + static async getDicomJson (queryOptions) { + let queryBuilder = new UpsQueryBuilder(queryOptions); + let q = queryBuilder.build(); + + let upsArray = await WorkItemModel.findAll({ + ...q, + attributes: ["json"], + limit: queryOptions.limit, + offset: queryOptions.skip + }); + + return await Promise.all(upsArray.map(async ups => { + let { json } = ups.toJSON(); + return json; + })); + } }; /** @type { import("sequelize").ModelAttributes } */ From b03ca342e26e1727894f91379d300f16be41ca6b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 25 Nov 2023 23:13:26 +0800 Subject: [PATCH 191/365] refactor: move `notAllowedAttributes` into class --- .../UPS-RS/service/update-workItem.service.js | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 5ae5849c..df7e02e7 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -12,23 +12,24 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); -const notAllowedAttributes = [ - "00080016", - "00080018", - "00100010", - "00100020", - "00100030", - "00100040", - "00380010", - "00380014", - "00081080", - "00081084", - "0040A370", - "00741224", - "00741000" -]; + class UpdateWorkItemService extends BaseWorkItemService { + static notAllowedAttributes = Object.freeze([ + "00080016", + "00080018", + "00100010", + "00100020", + "00100030", + "00100040", + "00380010", + "00380014", + "00081080", + "00081084", + "0040A370", + "00741224", + "00741000" + ]); /** * * @param {import('express').Request} req @@ -162,8 +163,8 @@ class UpdateWorkItemService extends BaseWorkItemService { * remove not allowed updating attribute in request work item */ adjustRequestWorkItem() { - for (let i = 0; i < notAllowedAttributes.length; i++) { - let notAllowedAttr = notAllowedAttributes[i]; + for (let i = 0; i < UpdateWorkItemService.notAllowedAttributes.length; i++) { + let notAllowedAttr = UpdateWorkItemService.notAllowedAttributes[i]; _.unset(this.requestWorkItem.dicomJson, notAllowedAttr); } } From 1768e9c9c1728400707c969453f07370386635ef Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 25 Nov 2023 23:30:26 +0800 Subject: [PATCH 192/365] feat: update save method in UPS - setPatient, setGeneralCode, setHumanPerformerName (all is association items) would performed when updating or creating - every op will create new item for association items - so, we need to remove original exist association items to do updating --- models/sql/po/upsWorkItem.po.js | 83 ++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index ae6d9540..772be782 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -1,4 +1,4 @@ -const { get, set } = require("lodash"); +const { get, set, cloneDeep } = require("lodash"); const { PersonNameModel } = require("../models/personName.model"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); @@ -59,33 +59,16 @@ class UpsWorkItemPersistentObject { } async save() { - let item = { - json: this.json, - upsInstanceUID : this.upsInstanceUID, - patientID : this.patientID, - transactionUID : this.transactionUID, - x00100020: this.x00100020, - x00741200: this.x00741200, - x00404010: vrValueTransform.DT(this.x00404010), - x00741204: this.x00741204, - x00741202: this.x00741202, - x00404036: this.x00404036, - x00404005: vrValueTransform.DT(this.x00404005), - x00404011: vrValueTransform.DT(this.x00404011), - x00380010: this.x00380010, - x00741000: this.x00741000, - x00380014_x00400031: this.admissionLocalEntityId, - x00380014_x00400032: this.admissionUniversalEntityId, - x00380014_x00400033: this.admissionUniversalEntityIdType - }; - - let upsWorkItemObj = WorkItemModel.build(item); + let upsWorkItemObj = WorkItemModel.build(this.getPersistentObject()); let [upsWorkItem, created] = await WorkItemModel.findOrCreate({ where: { upsInstanceUID: this.upsInstanceUID }, defaults: upsWorkItemObj.toJSON() }); + let tempUpsWorkItem = cloneDeep(upsWorkItem); + + await upsWorkItem.setPatient(this.patient); await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationNameCodeSequence); await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationClassCodeSequence); @@ -94,17 +77,11 @@ class UpsWorkItemPersistentObject { await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledWorkitemCodeSequence); await this.setGeneralCode(upsWorkItem, dictionary.keyword.InstitutionCodeSequence); await this.setHumanPerformerName(upsWorkItem); + await upsWorkItem.save(); - - if (created) { - await upsWorkItem.save(); - } else { - await WorkItemModel.update(item, { - where: { - upsInstanceUID: upsWorkItem.dataValues.upsInstanceUID - } - }); - } + if (!created) { + await this.removeAllAssociationItems(tempUpsWorkItem); + } return upsWorkItem; } @@ -128,6 +105,48 @@ class UpsWorkItemPersistentObject { await upsWorkItem[`set${dictionary.tag["00404037"]}`](nameOfHumanPerformer); } } + + async removeAllAssociationItems(upsWorkItem) { + const associationItemsNames = [ + "ScheduledStationNameCodeSequence", + "ScheduledStationClassCodeSequence", + "ScheduledStationGeographicLocationCodeSequence", + "HumanPerformerCodeSequence", + "ScheduledWorkitemCodeSequence", + "InstitutionCodeSequence", + "HumanPerformerName" + ]; + + for (let i = 0 ; i < associationItemsNames.length ; i++) { + let associationItemName = associationItemsNames[i]; + let associationItem = await upsWorkItem[`get${associationItemName}`](); + if (associationItem) { + await associationItem.destroy(); + } + } + } + + getPersistentObject() { + return { + json: this.json, + upsInstanceUID : this.upsInstanceUID, + patientID : this.patientID, + transactionUID : this.transactionUID, + x00100020: this.x00100020, + x00741200: this.x00741200, + x00404010: vrValueTransform.DT(this.x00404010), + x00741204: this.x00741204, + x00741202: this.x00741202, + x00404036: this.x00404036, + x00404005: vrValueTransform.DT(this.x00404005), + x00404011: vrValueTransform.DT(this.x00404011), + x00380010: this.x00380010, + x00741000: this.x00741000, + x00380014_x00400031: this.admissionLocalEntityId, + x00380014_x00400032: this.admissionUniversalEntityId, + x00380014_x00400033: this.admissionUniversalEntityIdType + }; + } } module.exports.UpsWorkItemPersistentObject = UpsWorkItemPersistentObject; \ No newline at end of file From 57ecdb814c86453088a9e8ff0d1682bbec915e0d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 26 Nov 2023 11:32:22 +0800 Subject: [PATCH 193/365] feat: add `Institution Code (00080082)` association --- models/sql/init.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/models/sql/init.js b/models/sql/init.js index f12398a0..7de2f1dc 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -137,8 +137,8 @@ async function init() { }); WorkItemModel.belongsTo(DicomCodeModel, { - as: dictionary.tag["00404025"], - foreignKey: "x00404025" + foreignKey: "x00404025", + as: dictionary.tag["00404025"] }); WorkItemModel.belongsTo(DicomCodeModel, { foreignKey: "x00404026", @@ -156,6 +156,10 @@ async function init() { foreignKey: "x00404018", as: dictionary.tag["00404018"] }); + WorkItemModel.belongsTo(DicomCodeModel, { + foreignKey: "x00080082", + as: dictionary.tag["00080082"] + }); WorkItemModel.belongsTo(PersonNameModel, { foreignKey: "x00404037", From 0277aba221e3856c3a2c943feacff5d47c3a523f Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 26 Nov 2023 11:45:06 +0800 Subject: [PATCH 194/365] refactor: move `findOneOrCreatePatient` into PatientModel - the process in `findOneOrCreatePatient` is creating or updating patient - Rename it to `updateOrCreatePatient` and move it to a more appropriate location: PatientModel --- .../UPS-RS/service/create-workItem.service.js | 10 +--------- models/sql/models/patient.model.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 8c5e21d7..aafa9502 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -18,7 +18,7 @@ class SqlCreateWorkItemService extends CreateWorkItemService { await this.dataAdjustBeforeCreatingUps(uid); await this.validateWorkItem(uid); - let patient = await this.findOneOrCreatePatient(); + let patient = await PatientModel.updateOrCreatePatient(this.requestWorkItem.dicomJson); let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); let savedWorkItem = await workItem.save(); @@ -28,14 +28,6 @@ class SqlCreateWorkItemService extends CreateWorkItemService { return workItem; } - async findOneOrCreatePatient() { - /** @type {PatientModel | null} */ - let patientPersistent = new PatientPersistentObject(this.requestWorkItem.dicomJson); - let patient = await patientPersistent.createPatient(); - - return patient; - } - async isUpsExist(uid) { return await WorkItemModel.findOne({ where: { diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index eb728dfe..a262a867 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -10,7 +10,16 @@ if (raccoonConfig.dicomDimseConfig.enableDimse) { Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; } -class PatientModel extends Model { }; +class PatientModel extends Model { + static async updateOrCreatePatient(patient) { + /** @type {PatientModel | null} */ + const { PatientPersistentObject } = require("../po/patient.po"); + let patientPersistent = new PatientPersistentObject(patient); + let bringPatient = await patientPersistent.createPatient(); + + return bringPatient; + } +}; PatientModel.init({ "x00100010": { From de32f10c5f41ae8e1247a7ad49b7c8e2d66e16af Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 26 Nov 2023 11:45:34 +0800 Subject: [PATCH 195/365] feat: implement update workitem API --- .../UPS-RS/service/update-workItem.service.js | 40 +++++++++++++++++++ .../controller/UPS-RS/update-workItem.js | 2 +- models/sql/models/workitems.model.js | 20 ++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js diff --git a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js new file mode 100644 index 00000000..6787d9d3 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -0,0 +1,40 @@ +const { WorkItemModel } = require("@dbModels/workItems.model"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { DicomJsonModel } = require("@dicom-json-model"); +const { UpdateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/update-workItem.service"); +const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); +const { set, get } = require("lodash"); +const { CreateWorkItemService } = require("./create-workItem.service"); + +class SqlUpdateWorkItemService extends UpdateWorkItemService { + constructor(req, res) { + super(req, res); + this.transactionUID = this.requestWorkItem.getString("00081195"); + set(this.requestWorkItem.dicomJson, "upsInstanceUID", this.request.params.workItem); + } + + async updateUps() { + this.workItem = await WorkItemModel.findOneWorkItemDicomJsonModel(this.request.params.workItem); + await this.checkRequestUpsIsValid(); + this.adjustRequestWorkItem(); + + let createService = new CreateWorkItemService(this.request, this.response); + let patient = await createService.findOneOrCreatePatient(); + let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); + let savedWorkItem = await workItem.save(); + //TODO: update UPS event + } + + /** + * replace not allowed updating attribute in request work item + */ + adjustRequestWorkItem() { + for (let i = 0; i < UpdateWorkItemService.notAllowedAttributes.length; i++) { + let notAllowedAttr = UpdateWorkItemService.notAllowedAttributes[i]; + let originalValueOfNotAllowedAttr = get(this.workItem.dicomJson, notAllowedAttr); + set(this.requestWorkItem.dicomJson, originalValueOfNotAllowedAttr); + } + } +} + +module.exports.UpdateWorkItemService = SqlUpdateWorkItemService; \ No newline at end of file diff --git a/api/dicom-web/controller/UPS-RS/update-workItem.js b/api/dicom-web/controller/UPS-RS/update-workItem.js index f1c77d2b..267ccfbb 100644 --- a/api/dicom-web/controller/UPS-RS/update-workItem.js +++ b/api/dicom-web/controller/UPS-RS/update-workItem.js @@ -1,6 +1,6 @@ const { UpdateWorkItemService -} = require("./service/update-workItem.service"); +} = require("@ups-service/update-workItem.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index fd800623..0c7875f2 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -4,6 +4,8 @@ const { vrTypeMapping } = require("../vrTypeMapping"); const { raccoonConfig } = require("@root/config-class"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { UpsQueryBuilder } = require("@root/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder"); +const { DicomJsonModel } = require("../dicom-json-model"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); let Common; if (raccoonConfig.dicomDimseConfig.enableDimse) { @@ -36,6 +38,24 @@ class WorkItemModel extends Model { return json; })); } + + static async findOneWorkItemDicomJsonModel(upsInstanceUID) { + let workItemObj = await WorkItemModel.findOne({ + where: { + upsInstanceUID: upsInstanceUID + } + }); + if (workItemObj) { + let {json} = workItemObj.toJSON(); + return new DicomJsonModel(json); + } else { + throw new DicomWebServiceError( + DicomWebStatusCodes.UPSDoesNotExist, + "The UPS instance not exist", + 404 + ); + } + } }; /** @type { import("sequelize").ModelAttributes } */ From 705e044a338f233c60d8002679bd4e541f39c897 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 26 Nov 2023 12:23:53 +0800 Subject: [PATCH 196/365] refactor(mongodb): move `findOneOrCreatePatient` into PatientModel --- .../UPS-RS/service/create-workItem.service.js | 17 +---------------- models/mongodb/models/patient.model.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 59ab0d19..bec256d9 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -28,7 +28,7 @@ class CreateWorkItemService extends BaseWorkItemService { await this.validateWorkItem(uid); let patientId = this.requestWorkItem.getString("00100020"); - let patient = await this.findOneOrCreatePatient(patientId); + let patient = await PatientModel.findOneOrCreatePatient(patientId, this.requestWorkItem.dicomJson); let workItem = new workItemModel(this.requestWorkItem.dicomJson); let savedWorkItem = await workItem.save(); @@ -107,21 +107,6 @@ class CreateWorkItemService extends BaseWorkItemService { this.triggerUpsEvents(); } - async findOneOrCreatePatient(patientId) { - /** @type {PatientModel | null} */ - let patient = await PatientModel.findOne({ - "00100020.Value": patientId - }); - - if (!patient) { - /** @type {PatientModel} */ - let patientObj = new PatientModel(this.requestWorkItem.dicomJson); - patient = await patientObj.save(); - } - - return patient; - } - async isUpsExist(uid) { return await workItemModel.findOne({ upsInstanceUID: uid diff --git a/models/mongodb/models/patient.model.js b/models/mongodb/models/patient.model.js index 97e18dfe..78ce4b8d 100644 --- a/models/mongodb/models/patient.model.js +++ b/models/mongodb/models/patient.model.js @@ -31,6 +31,25 @@ let patientSchemaOptions = _.merge( fields[tag] = 1; } return fields; + }, + /** + * + * @param {string} patientId + * @param {any} patient + */ + findOneOrCreatePatient: async function(patientId, patient) { + /** @type {PatientModel | null} */ + let foundPatient = await mongoose.model("patient").findOne({ + "00100020.Value": patientId + }); + + if (!foundPatient) { + /** @type {PatientModel} */ + let patientObj = new mongoose.model("patient")(patient); + patient = await patientObj.save(); + } + + return patient; } } } From 8f83836ad274c895dd5bcd9b2242103a49819493 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 1 Dec 2023 22:46:14 +0800 Subject: [PATCH 197/365] feat(sql): implement change work item state --- .../service/change-workItem-state.service.js | 35 +++++++++++++++++ .../UPS-RS/change-workItem-state.js | 2 +- models/sql/models/workitems.model.js | 38 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js diff --git a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js new file mode 100644 index 00000000..a1f851f1 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -0,0 +1,35 @@ +const { WorkItemModel } = require("@dbModels/workItems.model"); +const { ChangeWorkItemStateService } = require("@root/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service"); + +class SqlChangeWorkItemStateService extends ChangeWorkItemStateService { + /** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ + constructor(req, res) { + super(req, res); + } + + async changeWorkItemState() { + let foundWorkItem = await WorkItemModel.findOneWorkItemByUpsInstanceUID(this.request.params.workItem); + this.workItem = foundWorkItem.toDicomJsonModel(); + + this.workItemState = this.workItem.getString("00741000"); + this.workItemTransactionUID = this.workItem.getString("00081195"); + let requestState = this.requestState.getString("00741000"); + + if (requestState === "IN PROGRESS") { + this.inProgressChange(); + } else if (requestState === "CANCELED") { + this.cancelChange(); + } else if (requestState === "COMPLETED") { + this.completeChange(); + } + + await foundWorkItem.changeWorkItemState(this.requestState); + // TODO: change work item state event + } +} + +module.exports.ChangeWorkItemStateService = SqlChangeWorkItemStateService; \ No newline at end of file diff --git a/api/dicom-web/controller/UPS-RS/change-workItem-state.js b/api/dicom-web/controller/UPS-RS/change-workItem-state.js index 05593e71..ce353d7f 100644 --- a/api/dicom-web/controller/UPS-RS/change-workItem-state.js +++ b/api/dicom-web/controller/UPS-RS/change-workItem-state.js @@ -1,6 +1,6 @@ const { ChangeWorkItemStateService -} = require("./service/change-workItem-state.service"); +} = require("@ups-service/change-workItem-state.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index 0c7875f2..4db7b64a 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -6,6 +6,7 @@ const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { UpsQueryBuilder } = require("@root/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder"); const { DicomJsonModel } = require("../dicom-json-model"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { merge } = require("lodash"); let Common; if (raccoonConfig.dicomDimseConfig.enableDimse) { @@ -22,6 +23,27 @@ class WorkItemModel extends Model { return await Common.getAttributesFromJsonString(jsonStr); } + toDicomJsonModel() { + return new DicomJsonModel(this.json); + } + + /** + * + * @param {DicomJsonModel} changedStateWorkItemDicomJsonModel + */ + async changeWorkItemState(changedStateWorkItemDicomJsonModel) { + let changedWorkItemJson = merge(this.json, changedStateWorkItemDicomJsonModel.dicomJson); + this.transactionUID = changedStateWorkItemDicomJsonModel.getString("00081195"); + this.x00741000 = changedStateWorkItemDicomJsonModel.getString("00741000"); + this.json = { + ...this.json, + ...changedWorkItemJson + }; + // Let sequelize know json is changed + this.changed("json", true); + await this.save(); + } + static async getDicomJson (queryOptions) { let queryBuilder = new UpsQueryBuilder(queryOptions); let q = queryBuilder.build(); @@ -56,6 +78,22 @@ class WorkItemModel extends Model { ); } } + + static async findOneWorkItemByUpsInstanceUID(upsInstanceUID) { + let workItemObj = await WorkItemModel.findOne({ + where: { + upsInstanceUID: upsInstanceUID + } + }); + if (!workItemObj) { + throw new DicomWebServiceError( + DicomWebStatusCodes.UPSDoesNotExist, + "The UPS instance not exist", + 404 + ); + } + return workItemObj; + } }; /** @type { import("sequelize").ModelAttributes } */ From e077d59983977c0d4d203cdeef78c06a86efcda8 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 07:20:00 +0800 Subject: [PATCH 198/365] refactor: Initiate work item before canceling By moving this logic into a separate method, the code becomes more reusable --- api/dicom-web/controller/UPS-RS/service/cancel.service.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index f9833516..d74ad0f3 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -23,9 +23,12 @@ class CancelWorkItemService extends BaseWorkItemService { this.requestWorkItem = /** @type {Object[]} */(this.request.body).pop(); } - async cancel() { - + async initWorkItem() { this.workItem = await this.findOneWorkItem(this.upsInstanceUID); + } + + async cancel() { + await this.initWorkItem(); let procedureStepState = this.workItem.getString(dictionary.keyword.ProcedureStepState); if (procedureStepState === "IN PROGRESS") { From 717fc17729ed4a3fbdb80eba66d6bc1fa8fa344a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 08:27:58 +0800 Subject: [PATCH 199/365] feat: add ups global subscription model --- .../sql/models/upsGlobalSubscrption.model.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 models/sql/models/upsGlobalSubscrption.model.js diff --git a/models/sql/models/upsGlobalSubscrption.model.js b/models/sql/models/upsGlobalSubscrption.model.js new file mode 100644 index 00000000..226b2c91 --- /dev/null +++ b/models/sql/models/upsGlobalSubscrption.model.js @@ -0,0 +1,31 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); +const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); + +class UpsGlobalSubscriptionModel extends Model {}; + +UpsGlobalSubscriptionModel.init({ + aeTitle: { + type: DataTypes.TEXT + }, + subscribed: { + type: DataTypes.INTEGER, + defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED + }, + queryKeys: { + type: vrTypeMapping.JSON, + allowNull: true + }, + isDeletionLock: { + type: DataTypes.BOOLEAN, + defaultValue: false + } +}, { + sequelize: sequelizeInstance, + modelName: "UpsGlobalSubscription", + tableName: "UpsGlobalSubscription", + freezeTableName: true +}); + +module.exports.UpsGlobalSubscriptionModel = UpsGlobalSubscriptionModel; From 5ca36da52f4fff217f6259e15c97e466484c7550 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 08:49:51 +0800 Subject: [PATCH 200/365] feat(sql): add ups subscription model --- models/sql/models/upsSubscription.model.js | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 models/sql/models/upsSubscription.model.js diff --git a/models/sql/models/upsSubscription.model.js b/models/sql/models/upsSubscription.model.js new file mode 100644 index 00000000..f4a78de2 --- /dev/null +++ b/models/sql/models/upsSubscription.model.js @@ -0,0 +1,27 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); + +class UpsSubscriptionModel extends Model { +}; + +UpsSubscriptionModel.init({ + aeTitle: { + type: DataTypes.TEXT + }, + subscribed: { + type: DataTypes.INTEGER, + defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED + }, + isDeletionLock: { + type: DataTypes.BOOLEAN, + defaultValue: false + } +}, { + sequelize: sequelizeInstance, + modelName: "UpsSubscription", + tableName: "UpsSubscription", + freezeTableName: true +}); + +module.exports.UpsSubscriptionModel = UpsSubscriptionModel; From 65d5f9f9832f379276252e6bc7c8f7ffdf9f605c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 09:37:36 +0800 Subject: [PATCH 201/365] chore: correct name of `upsGlobalSubscription.model.js` --- ...odel.js => upsGlobalSubscription.model.js} | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) rename models/sql/models/{upsGlobalSubscrption.model.js => upsGlobalSubscription.model.js} (57%) diff --git a/models/sql/models/upsGlobalSubscrption.model.js b/models/sql/models/upsGlobalSubscription.model.js similarity index 57% rename from models/sql/models/upsGlobalSubscrption.model.js rename to models/sql/models/upsGlobalSubscription.model.js index 226b2c91..f391f062 100644 --- a/models/sql/models/upsGlobalSubscrption.model.js +++ b/models/sql/models/upsGlobalSubscription.model.js @@ -3,7 +3,11 @@ const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); -class UpsGlobalSubscriptionModel extends Model {}; +class UpsGlobalSubscriptionModel extends Model { + static async cursor(query) { + return new UpsGlobalSubscriptionModelCursor(query); + } +}; UpsGlobalSubscriptionModel.init({ aeTitle: { @@ -28,4 +32,25 @@ UpsGlobalSubscriptionModel.init({ freezeTableName: true }); +class UpsGlobalSubscriptionModelCursor { + /** + * + * @param {import("sequelize").FindOptions} query + */ + constructor(query) { + /** @type { import("sequelize").FindOptions } */ + this.query = query; + this.offset = 0; + this.item = undefined; + } + + async next() { + return await UpsGlobalSubscriptionModel.findOne({ + ...this.query, + offset: this.offset++ + }); + } +} + + module.exports.UpsGlobalSubscriptionModel = UpsGlobalSubscriptionModel; From 726e57d7065197f93c322aa6a22860bea94e88e3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 09:39:14 +0800 Subject: [PATCH 202/365] feat(sql): implement ups subscription and base work item service --- .../UPS-RS/service/base-workItem.service.js | 83 ++++++++++ .../UPS-RS/service/subscribe.service.js | 149 ++++++++++++++++++ models/sql/init.js | 3 + 3 files changed, 235 insertions(+) create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js new file mode 100644 index 00000000..ac3d7805 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -0,0 +1,83 @@ +const _ = require("lodash"); +const { DicomJsonModel } = require("@dicom-json-model"); +const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); +const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription.model"); +const { UpsSubscriptionModel } = require("@dbModels/upsSubscription.model"); +const { WorkItemModel } = require("@dbModels/workItems.model"); +const { BaseWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/base-workItem.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { UpsQueryBuilder } = require("./query/upsQueryBuilder"); +class SqlBaseWorkItemService extends BaseWorkItemService { + + constructor(req, res) { + super(req, res); + } + + async isAeTileSubscribed(aeTitle) { + let subscription = await UpsSubscriptionModel.findOne({ + where: { + aeTitle: aeTitle + } + }); + + if (!subscription) + return false; + + return subscription.subscribed === SUBSCRIPTION_STATE.SUBSCRIBED_LOCK || + subscription.subscribed === SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK; + } + + async getGlobalSubscriptionsCursor() { + return UpsGlobalSubscriptionModel.cursor({}); + } + + /** + * @param {DicomJsonModel} workItem + */ + async getHitGlobalSubscriptions(workItem) { + let globalSubscriptionsCursor = await this.getGlobalSubscriptionsCursor(); + let hitGlobalSubscriptions = []; + let globalSubscription = await globalSubscriptionsCursor.next(); + while (globalSubscription) { + if (!globalSubscription.queryKeys) { + hitGlobalSubscriptions.push(globalSubscription); + } else { + //TODO: ups global query builder + let query = convertAllQueryToDicomTag(globalSubscription.queryKeys); + _.set(query, "upsInstanceUID", workItem.dicomJson.upsInstanceUID); + let queryOptions = { + query: this.query, + requestParams: this.request.params + }; + let upsQueryBuilder = new UpsQueryBuilder(queryOptions); + let dbQuery = upsQueryBuilder.build(); + let count = await WorkItemModel.count({ + ...dbQuery + }); + if (count > 0) + hitGlobalSubscriptions.push(globalSubscription); + } + globalSubscription = await globalSubscriptionsCursor.next(); + } + return hitGlobalSubscriptions; + } + + async getHitSubscriptions(workItem) { + let hitSubscriptions = await UpsSubscriptionModel.findAll({ + include: [ + { + model: WorkItemModel, + where: { + upsInstanceUID: workItem.dicomJson._id + }, + required: true + } + ] + }); + + return hitSubscriptions; + } + +} + +module.exports.BaseWorkItemService = SqlBaseWorkItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js new file mode 100644 index 00000000..b9b6f3c4 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -0,0 +1,149 @@ +const _ = require("lodash"); +const { DicomJsonModel } = require("@dicom-json-model"); +const { UpsSubscriptionModel } = require("@dbModels/upsSubscription.model"); +const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription.model"); +const { + DicomWebServiceError, + DicomWebStatusCodes +} = require("@error/dicom-web-service"); +const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); +const { SubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/subscribe.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { WorkItemModel } = require("@models/sql/models/workItems.model"); +const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); + +class SqlSubscribeService extends SubscribeService { + + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + */ + constructor(req, res) { + super(req, res); + } + + async create() { + + if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID || + this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID) { + + this.query = convertAllQueryToDicomTag(this.request.query, false); + await this.createOrUpdateGlobalSubscription(); + } else { + let workItem = await WorkItemModel.findOneWorkItemByUpsInstanceUID(this.upsInstanceUID); + let workItemDicomJsonModel = workItem.toDicomJsonModel(); + await this.createOrUpdateSubscription(workItem); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, this.upsInstanceUID, this.stateReportOf(workItemDicomJsonModel), [this.subscriberAeTitle]); + } + + await this.triggerUpsEvents(); + } + + //#region Subscription + async findOneSubscription() { + return await UpsSubscriptionModel.findOne({ + where: { + aeTitle: this.subscriberAeTitle + } + }); + } + + /** + * + * @param {DicomJsonModel} workItem + * @returns + */ + async createOrUpdateSubscription(workItem) { + let subscription = await this.findOneSubscription(); + let subscribed = this.deletionLock ? SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK : SUBSCRIPTION_STATE.SUBSCRIBED_LOCK; + await this.updateWorkItemSubscription(workItem, subscribed); + if (!subscription) { + // Create + let subscriptionObj = UpsSubscriptionModel.build({ + aeTitle: this.subscriberAeTitle, + isDeletionLock: this.deletionLock, + subscribed: subscribed + }); + + await subscriptionObj.addUPSWorkItem(workItem); + let createdSubscription = await subscriptionObj.save(); + return createdSubscription; + } else { + // Update + subscription.isDeletionLock = this.deletionLock; + subscription.subscribed = subscribed; + if (!await subscription.hasUPSWorkItem(workItem)) { + subscription.addUPSWorkItem(workItem); + } + let updatedSubscription = await subscription.save(); + return updatedSubscription; + } + } + + async updateWorkItemSubscription(workItem, subscription) { + workItem.subscribed = subscription; + await workItem.save(); + } + //#endregion + + //#region Global Subscriptions + async createOrUpdateGlobalSubscription() { + let subscribed = this.deletionLock ? SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK : SUBSCRIPTION_STATE.SUBSCRIBED_LOCK; + let subscription = await this.findOneGlobalSubscription(); + if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID) this.query = undefined; + if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID && _.isEmpty(this.query)) { + throw new DicomWebServiceError( + DicomWebStatusCodes.InvalidArgumentValue, + `Missing "filter", The Filtered Worklist Subscription must have "filter"`, + 400 + ); + } + if (!subscription) { + //Create + let subscriptionObj = UpsGlobalSubscriptionModel.build({ + aeTitle: this.subscriberAeTitle, + isDeletionLock: this.deletionLock, + subscribed: subscribed, + queryKeys: this.query + }); + + let createdSubscription = await subscriptionObj.save(); + } else { + //Update + subscription.isDeletionLock = this.deletionLock; + subscription.subscribed = subscribed; + subscription.queryKeys = this.query; + subscription.changed("queryKeys"); + await subscription.save(); + } + + let notSubscribedWorkItems = await this.findNotSubscribedWorkItems(); + for(let notSubscribedWorkItem of notSubscribedWorkItems) { + let workItemDicomJson = new DicomJsonModel(notSubscribedWorkItem); + await this.createOrUpdateSubscription(workItemDicomJson); + + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItemDicomJson), [this.subscriberAeTitle]); + } + } + + async findOneGlobalSubscription() { + return await UpsGlobalSubscriptionModel.findOne({ + where: { + aeTitle: this.subscriberAeTitle + } + }); + } + //#endregion + + async findNotSubscribedWorkItems() { + return await WorkItemModel.findAll({ + where: { + subscribed: SUBSCRIPTION_STATE.NOT_SUBSCRIBED + } + }) || []; + } +} + + +module.exports.SubscribeService = SqlSubscribeService; \ No newline at end of file diff --git a/models/sql/init.js b/models/sql/init.js index 7de2f1dc..87b4f2c8 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -13,6 +13,7 @@ const { DicomContentSqModel } = require("./models/dicomContentSQ.model"); const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model"); const { WorkItemModel } = require("./models/workItems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { UpsSubscriptionModel } = require("./models/upsSubscription.model"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -165,6 +166,8 @@ async function init() { foreignKey: "x00404037", as: dictionary.tag["00404037"] }); + + UpsSubscriptionModel.hasMany(WorkItemModel); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({ From ba7073bf3a8ed4615968fcfcd71d5d4860dd764e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 09:39:43 +0800 Subject: [PATCH 203/365] refactor: module alias for `BaseWorkItemService` --- api/dicom-web/controller/UPS-RS/service/cancel.service.js | 2 +- .../controller/UPS-RS/service/change-workItem-state.service.js | 2 +- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/subscribe.service.js | 2 +- .../controller/UPS-RS/service/suspend-subscription.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js | 2 +- .../controller/UPS-RS/service/update-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/subscribe.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index d74ad0f3..d5384075 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -4,7 +4,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { BaseWorkItemService } = require("./base-workItem.service"); +const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { raccoonConfig } = require("@root/config-class"); diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index dba67314..ff9228e3 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -7,7 +7,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { BaseWorkItemService } = require("./base-workItem.service"); +const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); class ChangeWorkItemStateService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index bec256d9..d53890a7 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -7,7 +7,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { DicomJsonModel } = require("@dicom-json-model"); -const { BaseWorkItemService } = require("./base-workItem.service"); +const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); const { SubscribeService } = require("./subscribe.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index ec3d0e47..0804689e 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -9,7 +9,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); -const { BaseWorkItemService } = require("./base-workItem.service"); +const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); diff --git a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js index 3a3c47bc..e2bf8e60 100644 --- a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js +++ b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js @@ -4,7 +4,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { BaseWorkItemService } = require("./base-workItem.service"); +const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); class SuspendSubscribeService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 1e2bb664..be0a67c0 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -9,7 +9,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); -const { BaseWorkItemService } = require("./base-workItem.service"); +const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); class UnSubscribeService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index df7e02e7..8e7de270 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -7,7 +7,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { DicomJsonModel } = require("@dicom-json-model"); -const { BaseWorkItemService } = require("./base-workItem.service"); +const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); diff --git a/api/dicom-web/controller/UPS-RS/subscribe.js b/api/dicom-web/controller/UPS-RS/subscribe.js index 4ad936c1..eaef8621 100644 --- a/api/dicom-web/controller/UPS-RS/subscribe.js +++ b/api/dicom-web/controller/UPS-RS/subscribe.js @@ -1,6 +1,6 @@ const { SubscribeService -} = require("./service/subscribe.service"); +} = require("@ups-service/subscribe.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); From 0a43a4fe1bc23bb928c03d5fd9f1fe787c88a1f7 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 11:00:27 +0800 Subject: [PATCH 204/365] fix(sql): incorrect query property in `getHitSubscriptions` --- .../controller/UPS-RS/service/base-workItem.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js index ac3d7805..ebf8ccf0 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -68,7 +68,7 @@ class SqlBaseWorkItemService extends BaseWorkItemService { { model: WorkItemModel, where: { - upsInstanceUID: workItem.dicomJson._id + upsInstanceUID: workItem.dicomJson.upsInstanceUID }, required: true } From 1b858486d6f56e0b45c84b691b99ccfebee6902e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 11:01:32 +0800 Subject: [PATCH 205/365] fix: the argument should be work item model obj --- .../dicom-web/controller/UPS-RS/service/subscribe.service.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js index b9b6f3c4..01fdd617 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -120,10 +120,9 @@ class SqlSubscribeService extends SubscribeService { let notSubscribedWorkItems = await this.findNotSubscribedWorkItems(); for(let notSubscribedWorkItem of notSubscribedWorkItems) { - let workItemDicomJson = new DicomJsonModel(notSubscribedWorkItem); - await this.createOrUpdateSubscription(workItemDicomJson); + await this.createOrUpdateSubscription(notSubscribedWorkItem); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItemDicomJson), [this.subscriberAeTitle]); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, notSubscribedWorkItem.upsInstanceUID, this.stateReportOf(notSubscribedWorkItem.toDicomJsonModel()), [this.subscriberAeTitle]); } } From adbc75713cdac07319b9bbb6ad62103db643975e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 13:04:06 +0800 Subject: [PATCH 206/365] feat(sql): implement ups create subscription event --- .../controller/UPS-RS/service/create-workItem.service.js | 5 ++--- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js index aafa9502..4c51a4a0 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -1,6 +1,6 @@ const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); -const { WorkItemModel } = require("@models/sql/models/workItems.model"); +const { WorkItemModel } = require("@dbModels/workItems.model"); const { CreateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/create-workItem.service"); const { get, set } = require("lodash"); const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); @@ -22,8 +22,7 @@ class SqlCreateWorkItemService extends CreateWorkItemService { let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); let savedWorkItem = await workItem.save(); - //TODO: subscription - //this.triggerCreateEvent(savedWorkItem); + this.triggerCreateEvent(savedWorkItem); return workItem; } diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index d53890a7..611a5959 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -8,7 +8,7 @@ const { } = require("@error/dicom-web-service"); const { DicomJsonModel } = require("@dicom-json-model"); const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); -const { SubscribeService } = require("./subscribe.service"); +const { SubscribeService } = require("@ups-service/subscribe.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); From 1336ba778d339b234b2e1f3539ea54743f69acae Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 14:23:19 +0800 Subject: [PATCH 207/365] fix: filtered global subscription not working - missing `false` for `pushSuffixValue` of `convertAllQueryToDicomTag` method for SQL - remove invalid `this` of queryOptions --- .../controller/UPS-RS/service/base-workItem.service.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js index ebf8ccf0..6fc526d8 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -42,12 +42,10 @@ class SqlBaseWorkItemService extends BaseWorkItemService { if (!globalSubscription.queryKeys) { hitGlobalSubscriptions.push(globalSubscription); } else { - //TODO: ups global query builder - let query = convertAllQueryToDicomTag(globalSubscription.queryKeys); + let query = convertAllQueryToDicomTag(globalSubscription.queryKeys, false); _.set(query, "upsInstanceUID", workItem.dicomJson.upsInstanceUID); let queryOptions = { - query: this.query, - requestParams: this.request.params + query: query }; let upsQueryBuilder = new UpsQueryBuilder(queryOptions); let dbQuery = upsQueryBuilder.build(); From 85fe9163bc9e8f5d71bad7c0e0320cd4e33742fe Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 14:26:07 +0800 Subject: [PATCH 208/365] refactor: filename casting of require statement --- .../controller/UPS-RS/service/base-workItem.service.js | 2 +- .../controller/UPS-RS/service/change-workItem-state.service.js | 2 +- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- .../dicom-web/controller/UPS-RS/service/get-workItem.service.js | 2 +- .../dicom-web/controller/UPS-RS/service/subscribe.service.js | 2 +- .../controller/UPS-RS/service/update-workItem.service.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 6fc526d8..d1d6ecfa 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -3,7 +3,7 @@ const { DicomJsonModel } = require("@dicom-json-model"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription.model"); const { UpsSubscriptionModel } = require("@dbModels/upsSubscription.model"); -const { WorkItemModel } = require("@dbModels/workItems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { BaseWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { UpsQueryBuilder } = require("./query/upsQueryBuilder"); diff --git a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index a1f851f1..843c8d74 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -1,4 +1,4 @@ -const { WorkItemModel } = require("@dbModels/workItems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { ChangeWorkItemStateService } = require("@root/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service"); class SqlChangeWorkItemStateService extends ChangeWorkItemStateService { diff --git a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 4c51a4a0..7a64639a 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -1,6 +1,6 @@ const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); -const { WorkItemModel } = require("@dbModels/workItems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { CreateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/create-workItem.service"); const { get, set } = require("lodash"); const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); diff --git a/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 1e5f0da6..8dd40cbc 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { GetWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/get-workItem.service"); const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); -const { WorkItemModel } = require("@models/sql/models/workItems.model"); +const { WorkItemModel } = require("@models/sql/models/workitems.model"); class SqlGetWorkItemService extends GetWorkItemService { constructor(req, res) { diff --git a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js index 01fdd617..513f0550 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -9,7 +9,7 @@ const { const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); const { SubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/subscribe.service"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); -const { WorkItemModel } = require("@models/sql/models/workItems.model"); +const { WorkItemModel } = require("@models/sql/models/workitems.model"); const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); class SqlSubscribeService extends SubscribeService { diff --git a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 6787d9d3..f9153f22 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -1,4 +1,4 @@ -const { WorkItemModel } = require("@dbModels/workItems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); const { DicomJsonModel } = require("@dicom-json-model"); const { UpdateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/update-workItem.service"); From ee26929681bb515547b11aa44676a98a81459057 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 14:35:13 +0800 Subject: [PATCH 209/365] refactor: filename casting of require statement --- .../controller/UPS-RS/service/change-workItem-state.service.js | 2 +- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/get-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/subscribe.service.js | 2 +- api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js | 2 +- .../controller/UPS-RS/service/update-workItem.service.js | 2 +- models/sql/init.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index ff9228e3..f3536065 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const moment = require("moment"); const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const { WorkItemModel } = require("@models/mongodb/models/workItems.model"); +const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); const { DicomWebServiceError, DicomWebStatusCodes diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 611a5959..564030a1 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const workItemModel = require("@models/mongodb/models/workItems.model"); +const workItemModel = require("@models/mongodb/models/workitems.model"); const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { diff --git a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 4f92f0b0..dba5e727 100644 --- a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const workItemsModel = require("@models/mongodb/models/workItems.model"); +const workItemsModel = require("@models/mongodb/models/workitems.model"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index 0804689e..ef3e13a0 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const workItemModel = require("@models/mongodb/models/workItems.model"); +const workItemModel = require("@models/mongodb/models/workitems.model"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index be0a67c0..1dbe0e5d 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const workItemModel = require("@models/mongodb/models/workItems.model"); +const workItemModel = require("@models/mongodb/models/workitems.model"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 8e7de270..a4f0119a 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const workItemModel = require("@models/mongodb/models/workItems.model"); +const workItemModel = require("@models/mongodb/models/workitems.model"); const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { diff --git a/models/sql/init.js b/models/sql/init.js index 87b4f2c8..61bdc554 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -11,7 +11,7 @@ const { SeriesRequestAttributesModel } = require("./models/seriesRequestAttribut const { DicomCodeModel } = require("./models/dicomCode.model"); const { DicomContentSqModel } = require("./models/dicomContentSQ.model"); const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model"); -const { WorkItemModel } = require("./models/workItems.model"); +const { WorkItemModel } = require("./models/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UpsSubscriptionModel } = require("./models/upsSubscription.model"); From c3301a086e8837ba592c1fa5ae825505cbdec110 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 14:40:07 +0800 Subject: [PATCH 210/365] refactor: filename casting of require statement --- .../controller/UPS-RS/service/base-workItem.service.js | 2 +- models/sql/po/upsWorkItem.po.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 1aad629c..7f03a3f2 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -6,7 +6,7 @@ const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); -const { WorkItemModel } = require("@dbModels/workItems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); class BaseWorkItemService { diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index 772be782..65e02e73 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -4,7 +4,7 @@ const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { vrValueTransform } = require("./utils"); -const { WorkItemModel } = require("../models/workItems.model"); +const { WorkItemModel } = require("../models/workitems.model"); const { DicomCodeModel } = require("../models/dicomCode.model"); From c612d1f213f9ce1db0635e108738fd5b6b9be43b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 14:48:36 +0800 Subject: [PATCH 211/365] feat(sql): implement subscription fro change workitem state --- .../UPS-RS/service/base-workItem.service.js | 2 +- .../service/change-workItem-state.service.js | 20 +++++++++++++++++++ models/sql/models/workitems.model.js | 7 +++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js index d1d6ecfa..21898008 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -66,7 +66,7 @@ class SqlBaseWorkItemService extends BaseWorkItemService { { model: WorkItemModel, where: { - upsInstanceUID: workItem.dicomJson.upsInstanceUID + upsInstanceUID: workItem.getString("00080018") }, required: true } diff --git a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index 843c8d74..43f159f9 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -1,5 +1,6 @@ const { WorkItemModel } = require("@dbModels/workitems.model"); const { ChangeWorkItemStateService } = require("@root/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service"); +const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); class SqlChangeWorkItemStateService extends ChangeWorkItemStateService { /** @@ -28,7 +29,26 @@ class SqlChangeWorkItemStateService extends ChangeWorkItemStateService { } await foundWorkItem.changeWorkItemState(this.requestState); + await foundWorkItem.reload(); // TODO: change work item state event + this.triggerUpsChangeStateEvent(foundWorkItem); + } + + /** + * + * @param {WorkItemModel} updatedWorkItem + * @returns + */ + async triggerUpsChangeStateEvent(updatedWorkItem) { + let updatedWorkItemDicomJsonModelObj = updatedWorkItem.toDicomJsonModel(); + + let hitSubscriptions = await this.getHitSubscriptions(updatedWorkItemDicomJsonModelObj); + + if (hitSubscriptions.length === 0) return; + + let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, updatedWorkItemDicomJsonModelObj.dicomJson.upsInstanceUID, this.stateReportOf(updatedWorkItemDicomJsonModelObj), hitSubscriptionAeTitleArray); + this.triggerUpsEvents(); } } diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index 4db7b64a..a88a7971 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -114,6 +114,10 @@ const WorkItemSchema = { type: DataTypes.INTEGER, defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED }, + UpsSubscriptionId: { + type: DataTypes.INTEGER, + allowNull: true + }, //#region patient level "x00100020": { type: vrTypeMapping.LO, @@ -184,6 +188,9 @@ const WorkItemSchema = { "x00404036": { type: vrTypeMapping.LO }, + "x00404037": { + type: DataTypes.INTEGER + }, // #endregion "json": { type: vrTypeMapping.JSON From a7ef689531a9c37156af393db7cbe6f6a4d5a7d1 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 14:49:53 +0800 Subject: [PATCH 212/365] chore: rename `workItems.model.js` to `workitems.model.js` --- models/mongodb/models/{workItems.model.js => workitems.model.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename models/mongodb/models/{workItems.model.js => workitems.model.js} (100%) diff --git a/models/mongodb/models/workItems.model.js b/models/mongodb/models/workitems.model.js similarity index 100% rename from models/mongodb/models/workItems.model.js rename to models/mongodb/models/workitems.model.js From 9a91eb0364d58e61a85d6927291ea745ab0a3dfd Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 15:12:16 +0800 Subject: [PATCH 213/365] feat(sql): implement unsubscribe --- .../UPS-RS/service/unsubscribe.service.js | 105 ++++++++++++++++++ .../controller/UPS-RS/unsubscribe.js | 2 +- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js diff --git a/api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js new file mode 100644 index 00000000..cafc5d37 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -0,0 +1,105 @@ +const _ = require("lodash"); +const { DicomJsonModel } = require("@dicom-json-model"); +const { DicomCode } = require("@models/DICOM/code"); +const { + DicomWebServiceError, + DicomWebStatusCodes +} = require("@error/dicom-web-service"); +const { SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); +const { UnSubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/unsubscribe.service"); +const { WorkItemModel } = require("@models/sql/models/workitems.model"); +const { UpsSubscriptionModel } = require("@models/sql/models/upsSubscription.model"); +const { UpsGlobalSubscriptionModel } = require("@models/sql/models/upsGlobalSubscription.model"); + +class SqlUnSubscribeService extends UnSubscribeService { + + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + */ + constructor(req, res) { + super(req, res); + } + + async delete() { + + if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID || + this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID) { + + if (!(await this.isGlobalSubscriptionExist())) { + throw new DicomWebServiceError( + DicomWebStatusCodes.ProcessingFailure, + "The target Subscription was not found.", + 404 + ); + } + + await this.deleteGlobalSubscription(); + + } else { + let workItem = await WorkItemModel.findOneWorkItemByUpsInstanceUID(this.upsInstanceUID); + + if (!(await this.isSubscriptionExist())) { + throw new DicomWebServiceError( + DicomWebStatusCodes.ProcessingFailure, + "The target Subscription was not found.", + 404 + ); + } + + await this.deleteSubscription(workItem); + } + + } + + /** + * + * @param {WorkItemModel} workItem + */ + async deleteSubscription(workItem) { + let subscription = await UpsSubscriptionModel.findOne({ + where: { + aeTitle: this.subscriberAeTitle + } + }); + await subscription.removeUPSWorkItem(workItem); + } + + async deleteGlobalSubscription() { + + await Promise.all([ + UpsSubscriptionModel.destroy({ + where: { + aeTitle: this.subscriberAeTitle + } + }), + UpsGlobalSubscriptionModel.destroy({ + where: { + aeTitle: this.subscriberAeTitle + } + }) + ]); + + } + + async isSubscriptionExist() { + return await UpsSubscriptionModel.count({ + where: { + aeTitle: this.subscriberAeTitle + } + }) > 0; + } + + async isGlobalSubscriptionExist() { + return await UpsGlobalSubscriptionModel.count({ + where: { + aeTitle: this.subscriberAeTitle + } + }) > 0; + } + +} + + +module.exports.UnSubscribeService = SqlUnSubscribeService; \ No newline at end of file diff --git a/api/dicom-web/controller/UPS-RS/unsubscribe.js b/api/dicom-web/controller/UPS-RS/unsubscribe.js index e2cf36be..f6316372 100644 --- a/api/dicom-web/controller/UPS-RS/unsubscribe.js +++ b/api/dicom-web/controller/UPS-RS/unsubscribe.js @@ -1,6 +1,6 @@ const { UnSubscribeService -} = require("./service/unsubscribe.service"); +} = require("@ups-service/unsubscribe.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); From 3fd4114714790f5056167eeb53ce0d95d7f7976f Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 15:17:58 +0800 Subject: [PATCH 214/365] feat(sql): implement suspend global subscription --- .../service/suspend-subscription.service.js | 53 +++++++++++++++++++ .../controller/UPS-RS/suspend-subscription.js | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js diff --git a/api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js b/api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js new file mode 100644 index 00000000..2a0a24ea --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js @@ -0,0 +1,53 @@ +const _ = require("lodash"); +const { + DicomWebServiceError, + DicomWebStatusCodes +} = require("@error/dicom-web-service"); +const { SuspendSubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service"); +const { UpsGlobalSubscriptionModel } = require("@models/sql/models/upsGlobalSubscription.model"); + +class SqlSuspendSubscribeService extends SuspendSubscribeService { + + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + */ + constructor(req, res) { + super(req, res); + } + + async delete() { + + if (!(await this.isGlobalSubscriptionExist())) { + throw new DicomWebServiceError( + DicomWebStatusCodes.ProcessingFailure, + "The target Subscription was not found.", + 404 + ); + } + + await this.deleteGlobalSubscription(); + + } + + async deleteGlobalSubscription() { + await UpsGlobalSubscriptionModel.destroy({ + where: { + aeTitle: this.subscriberAeTitle + } + }); + } + + async isGlobalSubscriptionExist() { + return await UpsGlobalSubscriptionModel.destroy({ + where: { + aeTitle: this.subscriberAeTitle + } + }) > 0; + } + +} + + +module.exports.SuspendSubscribeService = SqlSuspendSubscribeService; \ No newline at end of file diff --git a/api/dicom-web/controller/UPS-RS/suspend-subscription.js b/api/dicom-web/controller/UPS-RS/suspend-subscription.js index 8161f525..a3128cdf 100644 --- a/api/dicom-web/controller/UPS-RS/suspend-subscription.js +++ b/api/dicom-web/controller/UPS-RS/suspend-subscription.js @@ -6,7 +6,7 @@ const { SuspendSubscribeService -} = require("./service/suspend-subscription.service"); +} = require("@ups-service/suspend-subscription.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); From e854c97ebd7fe2ec5551fd2466c5373bf0559d07 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 21:37:43 +0800 Subject: [PATCH 215/365] fix: missing adaptor for mongodb for audit message factory --- models/DICOM/audit/auditMessageFactory.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/models/DICOM/audit/auditMessageFactory.js b/models/DICOM/audit/auditMessageFactory.js index f370c347..09852421 100644 --- a/models/DICOM/audit/auditMessageFactory.js +++ b/models/DICOM/audit/auditMessageFactory.js @@ -24,6 +24,7 @@ const { EventType } = require("./eventType"); const { default: ActiveParticipantBuilder } = require("@dcm4che/audit/ActiveParticipantBuilder"); const { AuditMessages$UserIDTypeCode } = require("@dcm4che/audit/AuditMessages$UserIDTypeCode"); const { ParticipatingObjectFactory } = require("./participatingObjectFactory"); +const { raccoonConfig } = require("@root/config-class"); class AuditMessageFactory { constructor() { } @@ -349,8 +350,13 @@ class AuditMessageFactory { } getInstanceModel() { - const sequelizeInstance = require("@models/sql/instance"); - return sequelizeInstance.model("Instance"); + if (raccoonConfig.serverConfig.dbType === "sql") { + const sequelizeInstance = require("@models/mysql/instance"); + return sequelizeInstance.model("Instance"); + } else { + const mongoose = require("mongoose"); + return mongoose.model("dicom"); + } } } From bfb631272e54eaa2f12d41950434b141122edba7 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 2 Dec 2023 21:39:01 +0800 Subject: [PATCH 216/365] feat(mongo-mwl): implement first version of create MWL item --- .../controller/MWL-RS/create-mwlItem.js | 37 +++++ .../MWL-RS/service/create-mwlitem.service.js | 134 ++++++++++++++++ api/dicom-web/wml-rs.route.js | 39 +++++ models/DICOM/dicom-tags-mapping.js | 148 +++++++++++++++++- models/mongodb/models/mwlitems.model.js | 89 +++++++++++ routes.js | 1 + 6 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 api/dicom-web/controller/MWL-RS/create-mwlItem.js create mode 100644 api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js create mode 100644 api/dicom-web/wml-rs.route.js create mode 100644 models/mongodb/models/mwlitems.model.js diff --git a/api/dicom-web/controller/MWL-RS/create-mwlItem.js b/api/dicom-web/controller/MWL-RS/create-mwlItem.js new file mode 100644 index 00000000..f488b1db --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/create-mwlItem.js @@ -0,0 +1,37 @@ +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { CreateMwlItemService } = require("./service/create-mwlitem.service"); + +class CreateMwlItemController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "MWL-RS"); + } + + async mainProcess() { + try { + let createMwlItemService = new CreateMwlItemService(this.request, this.response); + let mwlItem = await createMwlItemService.create(); + return this.response + .set("Content-Type", "application/dicom+json") + .status(201) + .json(mwlItem); + + } catch (e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new CreateMwlItemController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js new file mode 100644 index 00000000..52c1a17f --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js @@ -0,0 +1,134 @@ +const _ = require("lodash"); +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { PatientModel } = require("@models/mongodb/models/patient.model"); +const { StudyModel } = require("@models/mongodb/models/study.model"); +const crypto = require('crypto'); +const moment = require("moment"); +const { UIDUtils } = require("@dcm4che/util/UIDUtils"); +const { + DicomWebServiceError, + DicomWebStatusCodes +} = require("@error/dicom-web-service"); +const { DicomJsonModel, BaseDicomJson } = require("@models/DICOM/dicom-json-model"); +const { v4: uuidV4 } = require("uuid"); +const shortHash = require("shorthash2"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); + +class CreateMwlItemService { + constructor(req, res) { + this.request = req; + this.response = res; + this.requestMwlItem = /** @type {Object} */(this.request.body); + /** @type {DicomJsonModel} */ + this.requestMwlItemDicomJsonModel = new DicomJsonModel(this.requestMwlItem); + this.apiLogger = new ApiLogger(req, "Create Mwl Item Service"); + this.apiLogger.addTokenValue(); + } + + async create() { + + await this.checkPatientExist(); + + let mwlItem = this.requestMwlItem[0]; + let mwlDicomJson = new BaseDicomJson(mwlItem); + + let spsItem = new BaseDicomJson(this.requestMwlItem[dictionary.keyword.ScheduledProcedureStepSequence]); + if (!spsItem.getValue(dictionary.keyword.ScheduledProcedureStepStatus)) { + _.set(mwlItem, dictionary.keyword.ScheduledProcedureStepStatus, { + vr: BaseDicomJson.getTagVrOfTag(dictionary.keyword.ScheduledProcedureStepStatus), + Value: [ + "SCHEDULED" + ] + }); + } + + if (!spsItem.getValue(dictionary.keyword.ScheduledProcedureStepID)) { + let spsID = shortHash(uuidV4()); + _.set(mwlItem, dictionary.keyword.ScheduledProcedureStepID, { + vr: BaseDicomJson.getTagVrOfTag(dictionary.keyword.ScheduledProcedureStepID), + Value: [ + `SPS-${spsID}` + ] + }); + } + + if (!mwlDicomJson.getValue(dictionary.keyword.RequestedProcedureID)) { + let rpID = shortHash(uuidV4()); + _.set(mwlItem, dictionary.keyword.RequestedProcedureID, { + vr: BaseDicomJson.getTagVrOfTag(dictionary.keyword.RequestedProcedureID), + Value: [ + `RP-${rpID}` + ] + }); + } + + if (!mwlDicomJson.getValue(dictionary.keyword.StudyInstanceUID)) { + let studyInstanceUID = await UIDUtils.createUID(); + _.set(mwlItem, dictionary.keyword.StudyInstanceUID, { + vr: BaseDicomJson.getTagVrOfTag(dictionary.keyword.StudyInstanceUID), + Value: [ + studyInstanceUID + ] + }); + } + + if (!mwlDicomJson.getValue(dictionary.keyword.AccessionNumber)) { + let accessionNumber = shortHash(uuidV4()); + _.set(mwlItem, dictionary.keyword.AccessionNumber, { + vr: BaseDicomJson.getTagVrOfTag(dictionary.keyword.AccessionNumber), + Value: [ + accessionNumber + ] + }); + } + + return await this.createOrUpdateMwl(mwlDicomJson); + } + + async checkPatientExist() { + let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); + let patientCount = await PatientModel.count({ + where: { + x00100020: patientID + } + }); + if (patientCount <= 0) { + throw new DicomWebServiceError( + DicomWebStatusCodes.MissingAttribute, + `Patient[id=${patientID}] does not exists`, + 404 + ); + } + } + + /** + * + * @param {BaseDicomJson} mwlDicomJson + */ + async createOrUpdateMwl(mwlDicomJson) { + let studyInstanceUID = mwlDicomJson.getValue(dictionary.keyword.StudyInstanceUID); + let foundMwl = await MwlItemModel.findOne({ + "0020000D.Value.0": studyInstanceUID + }); + + if (!foundMwl) { + // create + let mwlItemModelObj = new MwlItemModel(mwlDicomJson.dicomJson); + await mwlItemModelObj.save(); + this.apiLogger.logger.info(`create mwl item: ${studyInstanceUID}`); + return mwlItemModelObj.toDicomJson(); + } else { + // update + foundMwl.$set({ + ...mwlDicomJson.dicomJson + }); + await foundMwl.save(); + this.apiLogger.logger.info(`update mwl item: ${studyInstanceUID}`); + return foundMwl.toDicomJson(); + } + } + +} + +module.exports.CreateMwlItemService = CreateMwlItemService; \ No newline at end of file diff --git a/api/dicom-web/wml-rs.route.js b/api/dicom-web/wml-rs.route.js new file mode 100644 index 00000000..828649de --- /dev/null +++ b/api/dicom-web/wml-rs.route.js @@ -0,0 +1,39 @@ +const express = require("express"); +const Joi = require("joi"); +const { validateParams, intArrayJoi, validateByJoi } = require("@root/api/validator"); +const router = express(); + + +/** + * @openapi + * /dicom-web/mwlitems: + * post: + * tags: + * - MWL-RS + * description: > + * This transaction create or update a Modality WorkList item. + * requestBody: + * content: + * application/dicom+json: + * parameters: + * responses: + * "200": + * description: The workitem create successfully + */ +router.post("/mwlitems", + validateByJoi(Joi.array().items( + Joi.object({ + "00100020": Joi.object({ + vr: Joi.string().valid("LO").required(), + Value: Joi.array().items(Joi.string()).min(1).max(1).required() + }).required(), + "00400100": Joi.object({ + vr: Joi.string().valid("SQ").required(), + Value: Joi.array().items(Joi.object()).min(1).required() + }).required() + }) + ).min(1).max(1), "body", {allowUnknown: true}), + require("./controller/MWL-RS/create-mwlItem") +); + +module.exports = router; \ No newline at end of file diff --git a/models/DICOM/dicom-tags-mapping.js b/models/DICOM/dicom-tags-mapping.js index d889d338..063b57c2 100644 --- a/models/DICOM/dicom-tags-mapping.js +++ b/models/DICOM/dicom-tags-mapping.js @@ -618,5 +618,151 @@ module.exports.tagsNeedStore = { "00741224": { "vr": "SQ" } - } + }, + MWL: { + "00080005": { + "vr": "CS" + }, + "00080050": { + "vr": "SH" + }, + "00080054": { + "vr": "AE" + }, + "00080056": { + "vr": "CS" + }, + "00080090": { + "vr": "PN" + }, + "00080201": { + "vr": "SH" + }, + "00081110": { + "vr": "SQ" + }, + "00081120": { + "vr": "SQ" + }, + "00100010": { + "vr": "PN" + }, + "00100020": { + "vr": "LO" + }, + "00100021": { + "vr": "LO" + }, + "00100024": { + "vr": "SQ" + }, + "00100030": { + "vr": "DA" + }, + "00100040": { + "vr": "CS" + }, + "00100032": { + "vr": "TM" + }, + "00101002": { + "vr": "SQ" + }, + "00101001": { + "vr": "PN" + }, + "00100033": { + "vr": "LO" + }, + "00100034": { + "vr": "LO" + }, + "00100035": { + "vr": "CS" + }, + "00100050": { + "vr": "SQ" + }, + "00100101": { + "vr": "SQ" + }, + "00100200": { + "vr": "CS" + }, + "00100212": { + "vr": "UC" + }, + "00101030": { + "vr": "DS" + }, + "00102000": { + "vr": "LO" + }, + "00102110": { + "vr": "LO" + }, + "001021C0": { + "vr": "US" + }, + "0020000D": { + "vr": "UI" + }, + "00321032": { + "vr": "PN" + }, + "00321060": { + "vr": "LO" + }, + "00321064": { + "vr": "SQ" + }, + "00380010": { + "vr": "LO" + }, + "00380050": { + "vr": "LO" + }, + "00380300": { + "vr": "LO" + }, + "00380500": { + "vr": "LO" + }, + "00400001": { + "vr": "CS" + }, + "00400002": { + "vr": "DA" + }, + "00400003": { + "vr": "TM" + }, + "00400006": { + "vr": "PN" + }, + "00400009": { + "vr": "SH" + }, + "00400010": { + "vr": "SH" + }, + "00400011": { + "vr": "SH" + }, + "00400020": { + "vr": "CS" + }, + "00401001": { + "vr": "SH" + }, + "00401003": { + "vr": "SH" + }, + "00401004": { + "vr": "LO" + }, + "00403001": { + "vr": "LO" + } + } }; diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js new file mode 100644 index 00000000..4499f7c1 --- /dev/null +++ b/models/mongodb/models/mwlitems.model.js @@ -0,0 +1,89 @@ +const path = require("path"); +const mongoose = require("mongoose"); +const _ = require("lodash"); +const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); +const { getVRSchema } = require("../schema/dicomJsonAttribute"); + +let mwlItemSchema = new mongoose.Schema( + {}, + { + strict: true, + versionKey: false, + toObject: { + getters: true + }, + statics: { + /** + * + * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @returns + */ + getDicomJson: async function (queryOptions) { + // TODO: Not Yet Test + let { + mwlItem + } = queryOptions.requestParams; + + let query = mwlItem ? { + mwlInstanceUID: mwlItem + } : queryOptions.query; + + try { + let docs = await mongoose.model("mwlItems").find(query) + .limit(queryOptions.limit) + .skip(queryOptions.skip) + .setOptions({ + strictQuery: false + }) + .exec(); + + + let mwlDicomJson = docs.map((v) => { + let obj = v.toObject(); + delete obj._id; + delete obj.id; + return obj; + }); + + return mwlDicomJson; + + } catch (e) { + throw e; + } + } + }, + methods: { + toDicomJson: function () { + let obj = this.toObject(); + delete obj._id; + delete obj.id; + return obj; + } + } + } +); + +for (let tag in tagsNeedStore.MWL) { + let vr = tagsNeedStore.MWL[tag].vr; + let tagSchema = getVRSchema(vr); + mwlItemSchema.add({ + [tag]: tagSchema + }); +} + +for (let tag in tagsNeedStore.Patient) { + let vr = tagsNeedStore.Patient[tag].vr; + let tagSchema = getVRSchema(vr); + mwlItemSchema.add({ + [tag]: tagSchema + }); +} + +let mwlItemModel = mongoose.model( + "mwlItems", + mwlItemSchema, + "mwlItems" +); + +module.exports = mwlItemModel; +module.exports.MwlItemModel = mwlItemModel; diff --git a/routes.js b/routes.js index a994920c..3f7d4ee7 100644 --- a/routes.js +++ b/routes.js @@ -28,6 +28,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); + app.use("/dicom-web", require("./api/dicom-web/wml-rs.route")); app.use("/wado", require("./api/WADO-URI")); }; From d2742661b61f015b42a5f1733dc24d91c6e07e01 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 8 Dec 2023 20:40:43 +0800 Subject: [PATCH 217/365] chore: correct name to `mwl-rs.route` --- api/dicom-web/{wml-rs.route.js => mwl-rs.route.js} | 0 routes.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename api/dicom-web/{wml-rs.route.js => mwl-rs.route.js} (100%) diff --git a/api/dicom-web/wml-rs.route.js b/api/dicom-web/mwl-rs.route.js similarity index 100% rename from api/dicom-web/wml-rs.route.js rename to api/dicom-web/mwl-rs.route.js diff --git a/routes.js b/routes.js index 3f7d4ee7..60587fb4 100644 --- a/routes.js +++ b/routes.js @@ -28,7 +28,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/wado-rs-thumbnail.route")); app.use("/dicom-web", require("./api/dicom-web/delete.route")); app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); - app.use("/dicom-web", require("./api/dicom-web/wml-rs.route")); + app.use("/dicom-web", require("./api/dicom-web/mwl-rs.route")); app.use("/wado", require("./api/WADO-URI")); }; From b2902604ba3f502797496e8915d5b0d5609a4718 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 11:38:33 +0800 Subject: [PATCH 218/365] refactor: move `convertAllQueryToDicomTag` into base query service --- .../QIDO-RS/service/QIDO-RS.service.js | 3 +- .../UPS-RS/service/base-workItem.service.js | 2 +- .../UPS-RS/service/subscribe.service.js | 2 +- .../QIDO-RS/service/QIDO-RS.service.js | 47 +-------- .../UPS-RS/service/get-workItem.service.js | 2 +- .../UPS-RS/service/subscribe.service.js | 2 +- .../UPS-RS/service/unsubscribe.service.js | 1 - api/dicom-web/service/base-query.service.js | 99 +++++++++++++++++++ dimse-sql/queryBuilder.js | 2 +- test/QIDO-RS-Service/common.test.js | 4 +- test/QIDO-RS-Service/patient.test.js | 2 +- test/query.test.js | 2 +- 12 files changed, 110 insertions(+), 58 deletions(-) create mode 100644 api/dicom-web/service/base-query.service.js diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index d2d8f423..fdd27330 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -1,6 +1,7 @@ const _ = require("lodash"); -const { QidoRsService, convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); QidoRsService.prototype.initQuery_ = function () { let query = _.cloneDeep(this.request.query); diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 21898008..6514abd4 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -5,8 +5,8 @@ const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription. const { UpsSubscriptionModel } = require("@dbModels/upsSubscription.model"); const { WorkItemModel } = require("@dbModels/workitems.model"); const { BaseWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/base-workItem.service"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { UpsQueryBuilder } = require("./query/upsQueryBuilder"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); class SqlBaseWorkItemService extends BaseWorkItemService { constructor(req, res) { diff --git a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js index 513f0550..205cd35b 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -8,9 +8,9 @@ const { } = require("@error/dicom-web-service"); const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); const { SubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/subscribe.service"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { WorkItemModel } = require("@models/sql/models/workitems.model"); const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); class SqlSubscribeService extends SubscribeService { diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 5bb06e46..2d269133 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -9,6 +9,7 @@ const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); const { QueryPatientDicomJsonFactory, QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("@query-dicom-json-factory"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); const HierarchyQueryDicomJsonFactory = Object.freeze({ patient: QueryPatientDicomJsonFactory, @@ -134,50 +135,4 @@ class QidoRsService { } -/** - * Convert All of name(tags, keyword) of queries to tags number - * @param {Object} iParam The request query. - * @returns - */ -function convertAllQueryToDicomTag(iParam, pushSuffixValue=true) { - let keys = Object.keys(iParam); - let newQS = {}; - for (let i = 0; i < keys.length; i++) { - let keyName = keys[i]; - let keyNameSplit = keyName.split("."); - let newKeyNames = []; - for (let x = 0; x < keyNameSplit.length; x++) { - if (dictionary.keyword[keyNameSplit[x]]) { - newKeyNames.push(dictionary.keyword[keyNameSplit[x]]); - } else if (dictionary.tag[keyNameSplit[x]]) { - newKeyNames.push(keyNameSplit[x]); - } - } - let retKeyName; - if (newKeyNames.length === 0) { - throw new DicomWebServiceError( - DicomWebStatusCodes.InvalidArgumentValue, - `Invalid request query: ${keyNameSplit}`, - 400 - ); - } - - if (pushSuffixValue) { - if (newKeyNames.length >= 2) { - retKeyName = newKeyNames.map(v => v + ".Value").join("."); - } else { - newKeyNames.push("Value"); - retKeyName = newKeyNames.join("."); - } - } else { - retKeyName = newKeyNames.join("."); - } - - newQS[retKeyName] = iParam[keyName]; - } - return newQS; -} -//#endregion - module.exports.QidoRsService = QidoRsService; -module.exports.convertAllQueryToDicomTag = convertAllQueryToDicomTag; diff --git a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js index dba5e727..3906c345 100644 --- a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -3,7 +3,7 @@ const workItemsModel = require("@models/mongodb/models/workitems.model"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); -const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); class GetWorkItemService { constructor(req, res) { diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index ef3e13a0..41195066 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -11,7 +11,7 @@ const { const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); -const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); class SubscribeService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 1dbe0e5d..c48ca2c2 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -10,7 +10,6 @@ const { } = require("@error/dicom-web-service"); const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); -const { convertAllQueryToDicomTag } = require("../../QIDO-RS/service/QIDO-RS.service"); class UnSubscribeService extends BaseWorkItemService { diff --git a/api/dicom-web/service/base-query.service.js b/api/dicom-web/service/base-query.service.js new file mode 100644 index 00000000..d258d1a2 --- /dev/null +++ b/api/dicom-web/service/base-query.service.js @@ -0,0 +1,99 @@ +const _ = require("lodash"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); + + +class BaseQueryService { + constructor(req, res) { + this.request = req; + this.response = res; + + this.query = {}; + + /** + * @private + */ + this.limit_ = parseInt(this.request.query.limit) || 100; + delete this.request.query["limit"]; + + /** + * @private + */ + this.skip_ = parseInt(this.request.query.offset) || 0; + delete this.request.query["offset"]; + + /** + * @private + */ + this.includeFields_ = this.request.query["includefield"] || []; + + if (this.includeFields_.includes("all")) { + this.includeFields_ = ["all"]; + } + + delete this.request.query["includefield"]; + + this.initQuery_(); + } + + /** + * @private + */ + initQuery_() { + let query = _.cloneDeep(this.request.query); + let queryKeys = Object.keys(query).sort(); + for (let i = 0; i < queryKeys.length; i++) { + let queryKey = queryKeys[i]; + if (!query[queryKey]) delete query[queryKey]; + } + + this.query = convertAllQueryToDicomTag(query); + } +} + +/** + * Convert All of name(tags, keyword) of queries to tags number + * @param {Object} iParam The request query. + * @returns + */ +function convertAllQueryToDicomTag(iParam, pushSuffixValue=true) { + let keys = Object.keys(iParam); + let newQS = {}; + for (let i = 0; i < keys.length; i++) { + let keyName = keys[i]; + let keyNameSplit = keyName.split("."); + let newKeyNames = []; + for (let x = 0; x < keyNameSplit.length; x++) { + if (dictionary.keyword[keyNameSplit[x]]) { + newKeyNames.push(dictionary.keyword[keyNameSplit[x]]); + } else if (dictionary.tag[keyNameSplit[x]]) { + newKeyNames.push(keyNameSplit[x]); + } + } + let retKeyName; + if (newKeyNames.length === 0) { + throw new DicomWebServiceError( + DicomWebStatusCodes.InvalidArgumentValue, + `Invalid request query: ${keyNameSplit}`, + 400 + ); + } + + if (pushSuffixValue) { + if (newKeyNames.length >= 2) { + retKeyName = newKeyNames.map(v => v + ".Value").join("."); + } else { + newKeyNames.push("Value"); + retKeyName = newKeyNames.join("."); + } + } else { + retKeyName = newKeyNames.join("."); + } + + newQS[retKeyName] = iParam[keyName]; + } + return newQS; +} +//#endregion + +module.exports.convertAllQueryToDicomTag = convertAllQueryToDicomTag; \ No newline at end of file diff --git a/dimse-sql/queryBuilder.js b/dimse-sql/queryBuilder.js index 3227ebe4..abbec7e4 100644 --- a/dimse-sql/queryBuilder.js +++ b/dimse-sql/queryBuilder.js @@ -1,7 +1,7 @@ const _ = require("lodash"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { DimseQueryBuilder } = require("@root/dimse/queryBuilder"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); class SqlDimseQueryBuilder extends DimseQueryBuilder { diff --git a/test/QIDO-RS-Service/common.test.js b/test/QIDO-RS-Service/common.test.js index 82789ac5..52604525 100644 --- a/test/QIDO-RS-Service/common.test.js +++ b/test/QIDO-RS-Service/common.test.js @@ -3,11 +3,9 @@ const patientModel = require("../../models/mongodb/models/patient.model"); const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); -const { - convertAllQueryToDicomTag -} = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { convertRequestQueryToMongoQuery } = require("../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const moment = require("moment"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); describe("QIDO-RS Service Common Function", () => { diff --git a/test/QIDO-RS-Service/patient.test.js b/test/QIDO-RS-Service/patient.test.js index 54fb3740..0caca936 100644 --- a/test/QIDO-RS-Service/patient.test.js +++ b/test/QIDO-RS-Service/patient.test.js @@ -3,8 +3,8 @@ const patientModel = require("../../models/mongodb/models/patient.model"); const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); -const { convertAllQueryToDicomTag } = require("../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { QueryPatientDicomJsonFactory } = require("../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); describe("Patient QIDO-RS Service", async () => { let fakePatientData = { diff --git a/test/query.test.js b/test/query.test.js index 483bb96c..6b329aef 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -9,8 +9,8 @@ const dicomStudyModel = require("../models/mongodb/models/study.model"); const dicomSeriesModel = require("../models/mongodb/models/series.model"); const dicomModel = require("../models/mongodb/models/instance.model"); const { expect } = require("chai"); -const { convertAllQueryToDicomTag } = require("../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); describe("Query DICOM of study, series, and instance level", async () => { From 76a047a43fa56783034407eba04e0f9de6cde146 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 12:26:20 +0800 Subject: [PATCH 219/365] feat: change update logic for creating mwl item service - Add spsID to accurate finding exist unique mwl item --- .../MWL-RS/service/create-mwlitem.service.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js index 52c1a17f..2d364210 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js @@ -108,8 +108,17 @@ class CreateMwlItemService { */ async createOrUpdateMwl(mwlDicomJson) { let studyInstanceUID = mwlDicomJson.getValue(dictionary.keyword.StudyInstanceUID); + let spsItem = new BaseDicomJson(mwlDicomJson.getValue(dictionary.keyword.ScheduledProcedureStepSequence)); + let spsID = spsItem.getValue(dictionary.keyword.ScheduledProcedureStepID); let foundMwl = await MwlItemModel.findOne({ - "0020000D.Value.0": studyInstanceUID + $and: [ + { + "0020000D.Value.0": studyInstanceUID + }, + { + "00400100.Value.0.00400009.Value.0": spsID + } + ] }); if (!foundMwl) { From 0e1dc3aefe271128cead88677ab4ad037c707a05 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 12:27:11 +0800 Subject: [PATCH 220/365] fix: missing store `00400100` Scheduled Procedure Step Seq to DB --- models/DICOM/dicom-tags-mapping.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/DICOM/dicom-tags-mapping.js b/models/DICOM/dicom-tags-mapping.js index 063b57c2..273867ec 100644 --- a/models/DICOM/dicom-tags-mapping.js +++ b/models/DICOM/dicom-tags-mapping.js @@ -752,6 +752,9 @@ module.exports.tagsNeedStore = { "00400020": { "vr": "CS" }, + "00400100": { + "vr": "SQ" + }, "00401001": { "vr": "SH" }, From a98b14291cbb33677739dab41822bad7a5dd3d86 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 15:27:47 +0800 Subject: [PATCH 221/365] fix: VR of sequence is empty of `setValue` in BaseDicomJson --- models/DICOM/dicom-json-model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 233bc8f9..ac773bc7 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -76,7 +76,8 @@ class BaseDicomJson { } setValue(tag, value) { - let vrOfTag = _.get(dictionary.tagVR, `${tag}.vr`); + let lastTag = tag.split(".").at(-1); + let vrOfTag = _.get(dictionary.tagVR, `${lastTag}.vr`); _.set(this.dicomJson, `${tag}.vr`, vrOfTag); _.set(this.dicomJson, `${tag}.Value`, [value]); } From b65061e37bee2f8d326a74e6d835c1aa515a5402 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 15:34:32 +0800 Subject: [PATCH 222/365] fix: adjustment of sps is incorrect in create service - Make the structure of spsItem as following: ```json { "00400100": { "vr": "SQ" "Value": [...] } } ``` - use sequence path for correct getValue --- .../MWL-RS/service/create-mwlitem.service.js | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js index 2d364210..44ca6068 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js @@ -33,25 +33,26 @@ class CreateMwlItemService { let mwlItem = this.requestMwlItem[0]; let mwlDicomJson = new BaseDicomJson(mwlItem); - let spsItem = new BaseDicomJson(this.requestMwlItem[dictionary.keyword.ScheduledProcedureStepSequence]); - if (!spsItem.getValue(dictionary.keyword.ScheduledProcedureStepStatus)) { - _.set(mwlItem, dictionary.keyword.ScheduledProcedureStepStatus, { - vr: BaseDicomJson.getTagVrOfTag(dictionary.keyword.ScheduledProcedureStepStatus), - Value: [ - "SCHEDULED" - ] - }); + let spsItem = new BaseDicomJson({ + [dictionary.keyword.ScheduledProcedureStepSequence]: { + ...mwlItem[dictionary.keyword.ScheduledProcedureStepSequence] + } + }); + + let spsStatusPath = `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}`; + if (!spsItem.getValue(spsStatusPath)) { + spsItem.setValue(spsStatusPath, "SCHEDULED"); } - if (!spsItem.getValue(dictionary.keyword.ScheduledProcedureStepID)) { + let spsIDPath = `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepID}`; + if (!spsItem.getValue(spsIDPath)) { let spsID = shortHash(uuidV4()); - _.set(mwlItem, dictionary.keyword.ScheduledProcedureStepID, { - vr: BaseDicomJson.getTagVrOfTag(dictionary.keyword.ScheduledProcedureStepID), - Value: [ - `SPS-${spsID}` - ] - }); + spsItem.setValue(spsIDPath, `SPS-${spsID}`); } + mwlItem[dictionary.keyword.ScheduledProcedureStepSequence] = { + ...mwlItem[dictionary.keyword.ScheduledProcedureStepSequence], + ...spsItem.dicomJson + }; if (!mwlDicomJson.getValue(dictionary.keyword.RequestedProcedureID)) { let rpID = shortHash(uuidV4()); From 926de76c12c3cdb54ddfc70cd0c444e949a31ec4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 16:39:22 +0800 Subject: [PATCH 223/365] feat(mongodb): implement search for WML items API --- .../controller/MWL-RS/get-mwlItem.js | 40 +++++++++++++++++++ .../MWL-RS/service/get-mwlItem.service.js | 23 +++++++++++ api/dicom-web/mwl-rs.route.js | 20 ++++++++++ api/dicom-web/service/base-query.service.js | 1 + docs/swagger/parameters/dicomweb-common.yaml | 9 ++++- models/mongodb/models/mwlitems.model.js | 19 ++++----- models/mongodb/service.js | 13 ++++++ 7 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 api/dicom-web/controller/MWL-RS/get-mwlItem.js create mode 100644 api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js diff --git a/api/dicom-web/controller/MWL-RS/get-mwlItem.js b/api/dicom-web/controller/MWL-RS/get-mwlItem.js new file mode 100644 index 00000000..1aea7542 --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/get-mwlItem.js @@ -0,0 +1,40 @@ +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { GetMwlItemService } = require("./service/get-mwlItem.service"); + +class GetMwlItemController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "MWL-RS"); + } + + async mainProcess() { + try { + let mwlItems = await new GetMwlItemService(this.request, this.response).getMwlItems(); + + + if (mwlItems.length === 0) return this.response.status(204).end(); + + return this.response + .set("Content-Type", "application/dicom+json") + .status(200) + .json(mwlItems); + + } catch (e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new GetMwlItemController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js new file mode 100644 index 00000000..8690e81e --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js @@ -0,0 +1,23 @@ +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); + +class GetMwlItemService extends BaseQueryService { + constructor(req, res) { + super(req, res); + } + + async getMwlItems() { + let queryOptions = { + query: this.query, + skip: this.skip_, + limit: this.limit_, + includeFields: this.includeFields_ + }; + + let docs = await MwlItemModel.getDicomJson(queryOptions); + + return docs; + } +} + +module.exports.GetMwlItemService = GetMwlItemService; \ No newline at end of file diff --git a/api/dicom-web/mwl-rs.route.js b/api/dicom-web/mwl-rs.route.js index 828649de..056489ac 100644 --- a/api/dicom-web/mwl-rs.route.js +++ b/api/dicom-web/mwl-rs.route.js @@ -36,4 +36,24 @@ router.post("/mwlitems", require("./controller/MWL-RS/create-mwlItem") ); +/** + * @openapi + * /dicom-web/mwlitems: + * get: + * tags: + * - MWL-RS + * description: > + * This transaction search Modality WorkList items. + * parameters: + * - $ref: "#/components/parameters/filter" + * responses: + * "200": + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * type: array + */ +router.get("/mwlitems",require("./controller/MWL-RS/get-mwlItem")); + module.exports = router; \ No newline at end of file diff --git a/api/dicom-web/service/base-query.service.js b/api/dicom-web/service/base-query.service.js index d258d1a2..3a023e45 100644 --- a/api/dicom-web/service/base-query.service.js +++ b/api/dicom-web/service/base-query.service.js @@ -96,4 +96,5 @@ function convertAllQueryToDicomTag(iParam, pushSuffixValue=true) { } //#endregion +module.exports.BaseQueryService = BaseQueryService; module.exports.convertAllQueryToDicomTag = convertAllQueryToDicomTag; \ No newline at end of file diff --git a/docs/swagger/parameters/dicomweb-common.yaml b/docs/swagger/parameters/dicomweb-common.yaml index b2c1448b..2086ed30 100644 --- a/docs/swagger/parameters/dicomweb-common.yaml +++ b/docs/swagger/parameters/dicomweb-common.yaml @@ -28,4 +28,11 @@ components: "image/jpeg": schema: type: string - format: byte \ No newline at end of file + format: byte + parameters: + "filter": + description: "{attributeID}={value}; {attributeID} = {dicomTag} | {dicomKeyword} | {dicomTag}.{attributeID} | {dicomKeyword}.{attributeID}" + schema: + type: array + items: + type: string \ No newline at end of file diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 4499f7c1..60ecb613 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -3,6 +3,7 @@ const mongoose = require("mongoose"); const _ = require("lodash"); const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); +const { IncludeFieldsFactory } = require("../service"); let mwlItemSchema = new mongoose.Schema( {}, @@ -19,17 +20,9 @@ let mwlItemSchema = new mongoose.Schema( * @returns */ getDicomJson: async function (queryOptions) { - // TODO: Not Yet Test - let { - mwlItem - } = queryOptions.requestParams; - - let query = mwlItem ? { - mwlInstanceUID: mwlItem - } : queryOptions.query; - + let projection = mongoose.model("mwlItems").getDicomJsonProjection(queryOptions.includeFields); try { - let docs = await mongoose.model("mwlItems").find(query) + let docs = await mongoose.model("mwlItems").find(queryOptions.query, projection) .limit(queryOptions.limit) .skip(queryOptions.skip) .setOptions({ @@ -37,7 +30,7 @@ let mwlItemSchema = new mongoose.Schema( }) .exec(); - + let mwlDicomJson = docs.map((v) => { let obj = v.toObject(); delete obj._id; @@ -50,6 +43,10 @@ let mwlItemSchema = new mongoose.Schema( } catch (e) { throw e; } + }, + getDicomJsonProjection: function (includeFields) { + let includeFieldsFactory = new IncludeFieldsFactory(includeFields); + return includeFieldsFactory.getMwlLevelFields(); } }, methods: { diff --git a/models/mongodb/service.js b/models/mongodb/service.js index fe7ab819..6e6a98ca 100644 --- a/models/mongodb/service.js +++ b/models/mongodb/service.js @@ -320,6 +320,19 @@ class IncludeFieldsFactory { }; } + getMwlLevelFields() { + if (this.all) { + return {}; + } + + let fields = {}; + for (let tag in tagsOfRequiredMatching.Mwl) { + fields[tag] = 1; + } + + return fields; + } + /** * @private */ From a19caa7f5cbe08084695db3d5f22274d9980e025 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 18:08:55 +0800 Subject: [PATCH 224/365] feat(mongodb): implement count MWL items --- .../controller/MWL-RS/count-mwlItem.js | 39 +++++++++++++++++++ .../MWL-RS/service/count-mwlItem.service.js | 15 +++++++ api/dicom-web/mwl-rs.route.js | 22 +++++++++++ models/mongodb/models/mwlitems.model.js | 3 ++ 4 files changed, 79 insertions(+) create mode 100644 api/dicom-web/controller/MWL-RS/count-mwlItem.js create mode 100644 api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js diff --git a/api/dicom-web/controller/MWL-RS/count-mwlItem.js b/api/dicom-web/controller/MWL-RS/count-mwlItem.js new file mode 100644 index 00000000..2f778ecd --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/count-mwlItem.js @@ -0,0 +1,39 @@ +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { GetMwlItemCountService } = require("./service/count-mwlItem.service"); + +class GetMwlItemCountController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "MWL-RS"); + } + + async mainProcess() { + try { + let getMwlItemCountService = new GetMwlItemCountService(this.request, this.response); + let count = await getMwlItemCountService.getMwlItemCount(); + return this.response + .set("Content-Type", "application/dicom+json") + .status(200) + .json({ + count + }); + + } catch (e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new GetMwlItemCountController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js new file mode 100644 index 00000000..4dd5e9a7 --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js @@ -0,0 +1,15 @@ +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); + +class GetMwlItemCountService extends BaseQueryService { + constructor(req, res) { + super(req, res); + } + + async getMwlItemCount() { + + return await MwlItemModel.getCount(this.query); + } +} + +module.exports.GetMwlItemCountService = GetMwlItemCountService; \ No newline at end of file diff --git a/api/dicom-web/mwl-rs.route.js b/api/dicom-web/mwl-rs.route.js index 056489ac..1eed1c34 100644 --- a/api/dicom-web/mwl-rs.route.js +++ b/api/dicom-web/mwl-rs.route.js @@ -56,4 +56,26 @@ router.post("/mwlitems", */ router.get("/mwlitems",require("./controller/MWL-RS/get-mwlItem")); +/** + * @openapi + * /dicom-web/mwlitems/count: + * get: + * tags: + * - MWL-RS + * description: > + * This transaction get Modality WorkList items count. + * parameters: + * - $ref: "#/components/parameters/filter" + * responses: + * "200": + * description: Query successfully + * content: + * "application/dicom+json": + * schema: + * properties: + * count: + * type: number + */ +router.get("/mwlitems/count",require("./controller/MWL-RS/count-mwlItem")); + module.exports = router; \ No newline at end of file diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 60ecb613..23c950af 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -47,6 +47,9 @@ let mwlItemSchema = new mongoose.Schema( getDicomJsonProjection: function (includeFields) { let includeFieldsFactory = new IncludeFieldsFactory(includeFields); return includeFieldsFactory.getMwlLevelFields(); + }, + getCount: async function (query) { + return await mongoose.model("mwlItems").countDocuments(query); } }, methods: { From 3e022d1f8753eb28b5a8073027db17ba6150eeeb Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 9 Dec 2023 18:31:50 +0800 Subject: [PATCH 225/365] fix(mongodb): get/count MWL items API broken - Missing `convertRequestQueryToMongoQuery` for getting MWL items --- .../controller/MWL-RS/service/count-mwlItem.service.js | 3 ++- .../controller/MWL-RS/service/get-mwlItem.service.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js index 4dd5e9a7..449059be 100644 --- a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js @@ -1,5 +1,6 @@ const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); +const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); class GetMwlItemCountService extends BaseQueryService { constructor(req, res) { @@ -7,7 +8,7 @@ class GetMwlItemCountService extends BaseQueryService { } async getMwlItemCount() { - + this.query = (await convertRequestQueryToMongoQuery(this.query)).$match; return await MwlItemModel.getCount(this.query); } } diff --git a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js index 8690e81e..e277b18e 100644 --- a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js @@ -1,5 +1,6 @@ const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); +const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); class GetMwlItemService extends BaseQueryService { constructor(req, res) { @@ -7,8 +8,9 @@ class GetMwlItemService extends BaseQueryService { } async getMwlItems() { + let query = (await convertRequestQueryToMongoQuery(this.query)).$match; let queryOptions = { - query: this.query, + query, skip: this.skip_, limit: this.limit_, includeFields: this.includeFields_ From 4c2c0bf115da67ff374ebaf3b3c0880c573cfabd Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 10 Dec 2023 10:39:46 +0800 Subject: [PATCH 226/365] feat(mongodb): implement MWL delete API --- .../controller/MWL-RS/delete-mwlItem.js | 38 +++++++++++++++++++ .../MWL-RS/service/delete-mwlItem.service.js | 22 +++++++++++ api/dicom-web/mwl-rs.route.js | 23 +++++++++++ docs/swagger/parameters/dicomweb-common.yaml | 1 + docs/swagger/parameters/mwl.yaml | 8 ++++ error/dicom-web-service.js | 1 + models/mongodb/models/mwlitems.model.js | 13 +++++++ 7 files changed, 106 insertions(+) create mode 100644 api/dicom-web/controller/MWL-RS/delete-mwlItem.js create mode 100644 api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js create mode 100644 docs/swagger/parameters/mwl.yaml diff --git a/api/dicom-web/controller/MWL-RS/delete-mwlItem.js b/api/dicom-web/controller/MWL-RS/delete-mwlItem.js new file mode 100644 index 00000000..d038ae12 --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/delete-mwlItem.js @@ -0,0 +1,38 @@ +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { DeleteMwlItemService } = require("./service/delete-mwlItem.service"); + +class DeleteMwlItemCountController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "MWL-RS"); + } + + async mainProcess() { + try { + let deleteMwlItemService = new DeleteMwlItemService(this.request, this.response); + await deleteMwlItemService.deleteMwlItem(); + + return this.response + .set("Content-Type", "application/dicom+json") + .status(200) + .send(); + + } catch (e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new DeleteMwlItemCountController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js new file mode 100644 index 00000000..7400c9d7 --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js @@ -0,0 +1,22 @@ +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); + +class DeleteMwlItemService { + constructor(req, res) { + /** @type { import("express").Request } */ + this.request = req; + /** @type { import("express").Response } */ + this.response = res; + } + + async deleteMwlItem() { + const { studyUID, spsID } = this.request.params; + let { deletedCount } = await MwlItemModel.deleteByStudyInstanceUIDAndSpsID(studyUID, spsID); + + if (!deletedCount) { + throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchSOPInstance, "Modality Worklist Item not found.", 404); + } + } +} + +module.exports.DeleteMwlItemService = DeleteMwlItemService; \ No newline at end of file diff --git a/api/dicom-web/mwl-rs.route.js b/api/dicom-web/mwl-rs.route.js index 1eed1c34..0915de9d 100644 --- a/api/dicom-web/mwl-rs.route.js +++ b/api/dicom-web/mwl-rs.route.js @@ -78,4 +78,27 @@ router.get("/mwlitems",require("./controller/MWL-RS/get-mwlItem")); */ router.get("/mwlitems/count",require("./controller/MWL-RS/count-mwlItem")); +/** + * @openapi + * /dicom-web/mwlitems/{studyUID}/{spsID}: + * delete: + * tags: + * - MWL-RS + * description: > + * This transaction deletes a Modality WorkList item. + * requestBody: + * content: + * application/dicom+json: + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/spsID" + * responses: + * "204": + * description: Delete successfully + */ +router.delete("/mwlitems/:studyUID/:spsID", validateParams({ + studyUID: Joi.string().required(), + spsID: Joi.string().required() +}, "params", undefined), require("./controller/MWL-RS/delete-mwlItem")); + module.exports = router; \ No newline at end of file diff --git a/docs/swagger/parameters/dicomweb-common.yaml b/docs/swagger/parameters/dicomweb-common.yaml index 2086ed30..2175fd21 100644 --- a/docs/swagger/parameters/dicomweb-common.yaml +++ b/docs/swagger/parameters/dicomweb-common.yaml @@ -32,6 +32,7 @@ components: parameters: "filter": description: "{attributeID}={value}; {attributeID} = {dicomTag} | {dicomKeyword} | {dicomTag}.{attributeID} | {dicomKeyword}.{attributeID}" + in: query schema: type: array items: diff --git a/docs/swagger/parameters/mwl.yaml b/docs/swagger/parameters/mwl.yaml new file mode 100644 index 00000000..3c193a19 --- /dev/null +++ b/docs/swagger/parameters/mwl.yaml @@ -0,0 +1,8 @@ +components: + parameters: + spsID: + in: path + name: spsID + required: true + schema: + type: string \ No newline at end of file diff --git a/error/dicom-web-service.js b/error/dicom-web-service.js index b2cd090a..1e517517 100644 --- a/error/dicom-web-service.js +++ b/error/dicom-web-service.js @@ -1,6 +1,7 @@ const DicomWebStatusCodes = { "InvalidAttributeValue": "0106", "DuplicateSOPinstance": "0111", + "NoSuchSOPInstance": "0112", "InvalidArgumentValue": "0115", "MissingAttribute": "0120", "ProcessingFailure": "0272", diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 23c950af..ca1de43c 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -4,6 +4,7 @@ const _ = require("lodash"); const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { IncludeFieldsFactory } = require("../service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); let mwlItemSchema = new mongoose.Schema( {}, @@ -50,6 +51,18 @@ let mwlItemSchema = new mongoose.Schema( }, getCount: async function (query) { return await mongoose.model("mwlItems").countDocuments(query); + }, + deleteByStudyInstanceUIDAndSpsID: async function(studyUID, spsID) { + return await mongoose.model("mwlItems").deleteMany({ + $and: [ + { + [`${dictionary.keyword.StudyInstanceUID}.Value.0`]: studyUID + }, + { + [`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepID}.Value.0`]: spsID + } + ] + }); } }, methods: { From 302b2bda8e0cd8ec8435267ebb2d5a56846068f8 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 11:26:59 +0800 Subject: [PATCH 227/365] feat(mongodb): implement change status of mwl item --- .../MWL-RS/change-mwlItem-status.js | 39 ++++++++++++++++++ .../MWL-RS/service/change-mwlItem-status.js | 41 +++++++++++++++++++ api/dicom-web/mwl-rs.route.js | 30 ++++++++++++++ docs/swagger/parameters/mwl.yaml | 15 +++++++ 4 files changed, 125 insertions(+) create mode 100644 api/dicom-web/controller/MWL-RS/change-mwlItem-status.js create mode 100644 api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js diff --git a/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js new file mode 100644 index 00000000..d532d9d0 --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js @@ -0,0 +1,39 @@ +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { GetMwlItemCountService } = require("./service/count-mwlItem.service"); +const { ChangeMwlItemStatusService } = require("./service/change-mwlItem-status"); + +class ChangeMwlItemStatusCountController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "MWL-RS"); + } + + async mainProcess() { + try { + let changeMwlItemService = new ChangeMwlItemStatusService(this.request, this.response); + let changedMwlItems = await changeMwlItemService.changeMwlItemsStatus(); + + return this.response + .set("Content-Type", "application/dicom+json") + .status(200) + .json(changedMwlItems); + + } catch (e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new ChangeMwlItemStatusCountController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js new file mode 100644 index 00000000..e0e0ba2c --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js @@ -0,0 +1,41 @@ +const _ = require("lodash"); +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); + +class ChangeMwlItemStatusService { + constructor(req, res) { + /** @type {import("express").Request} */ + this.request = req; + /** @type {import("express").Response} */ + this.response = res; + } + + async changeMwlItemsStatus() { + let { status } = this.request.params; + let mwlItem = await this.getMwlItemByStudyUIDAndSpsID(); + if (!mwlItem) { + throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "No such object instance", 404); + } + + _.set(mwlItem, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); + await mwlItem.save(); + + return mwlItem.toDicomJson(); + } + + async getMwlItemByStudyUIDAndSpsID() { + return await MwlItemModel.findOne({ + $and: [ + { + "00400100.Value.0.00400009.Value.0": this.request.params.spsID + }, + { + "0020000D.Value.0": this.request.params.studyUID + } + ] + }); + } +} + +module.exports.ChangeMwlItemStatusService = ChangeMwlItemStatusService; \ No newline at end of file diff --git a/api/dicom-web/mwl-rs.route.js b/api/dicom-web/mwl-rs.route.js index 0915de9d..b8ae6ee6 100644 --- a/api/dicom-web/mwl-rs.route.js +++ b/api/dicom-web/mwl-rs.route.js @@ -101,4 +101,34 @@ router.delete("/mwlitems/:studyUID/:spsID", validateParams({ spsID: Joi.string().required() }, "params", undefined), require("./controller/MWL-RS/delete-mwlItem")); + +/** + * @openapi + * /dicom-web/mwlitems/{studyUID}/{spsID}/status/{spsStatus}: + * post: + * tags: + * - MWL-RS + * description: > + * This transaction create or update a Modality WorkList item. + * requestBody: + * content: + * application/dicom+json: + * parameters: + * - $ref: "#/components/parameters/studyUID" + * - $ref: "#/components/parameters/spsID" + * - $ref: "#/components/parameters/spsStatus" + * responses: + * "200": + * description: change status of mwl item successfully + */ +router.post("/mwlitems/:studyUID/:spsID/status/:status", + validateByJoi( + Joi.object({ + studyUID: Joi.string().required(), + spsID: Joi.string().required(), + status: Joi.string().valid("SCHEDULED", "ARRIVED", "READY", "STARTED", "DEPARTED", "CANCELED", "DISCONTINUED", "COMPLETED").required() + }), "params", {allowUnknown: false}), + require("./controller/MWL-RS/change-mwlItem-status") +); + module.exports = router; \ No newline at end of file diff --git a/docs/swagger/parameters/mwl.yaml b/docs/swagger/parameters/mwl.yaml index 3c193a19..f5459729 100644 --- a/docs/swagger/parameters/mwl.yaml +++ b/docs/swagger/parameters/mwl.yaml @@ -4,5 +4,20 @@ components: in: path name: spsID required: true + schema: + type: string + spsStatus: + in: path + name: spsStatus + required: true + enum: + - SCHEDULED + - ARRIVED + - READY + - STARTED + - DEPARTED + - CANCELED + - DISCONTINUED + - COMPLETED schema: type: string \ No newline at end of file From 2e9a48d5fb9f2fd09fef65f5d0cfa49cc47448d3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 15:47:16 +0800 Subject: [PATCH 228/365] feat(mongodb): implement change filtered mwl items' status --- .../MWL-RS/change-filtered-mwlItem-status.js | 40 +++++++++++++++++++ .../MWL-RS/change-mwlItem-status.js | 7 ++-- .../service/change-filtered-mwlItem-status.js | 34 ++++++++++++++++ api/dicom-web/mwl-rs.route.js | 26 ++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js create mode 100644 api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js diff --git a/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js new file mode 100644 index 00000000..3c9ef4af --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js @@ -0,0 +1,40 @@ +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { ChangeFilteredMwlItemStatusService } = require("./service/change-filtered-mwlItem-status"); + +class ChangeFilteredMwlItemStatusController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "MWL-RS"); + } + + async mainProcess() { + try { + let changeFilteredMwlItemService = new ChangeFilteredMwlItemStatusService(this.request, this.response); + let changedMwlItemsCount = await changeFilteredMwlItemService.changeMwlItemsStatus(); + + return this.response + .set("Content-Type", "application/dicom+json") + .status(200) + .json({ + updatedCount: changedMwlItemsCount + }); + + } catch (e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new ChangeFilteredMwlItemStatusController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js index d532d9d0..c80492f4 100644 --- a/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js @@ -1,10 +1,9 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { GetMwlItemCountService } = require("./service/count-mwlItem.service"); const { ChangeMwlItemStatusService } = require("./service/change-mwlItem-status"); -class ChangeMwlItemStatusCountController extends Controller { +class ChangeMwlItemStatusController extends Controller { constructor(req, res) { super(req, res); this.apiLogger = new ApiLogger(this.request, "MWL-RS"); @@ -14,7 +13,7 @@ class ChangeMwlItemStatusCountController extends Controller { try { let changeMwlItemService = new ChangeMwlItemStatusService(this.request, this.response); let changedMwlItems = await changeMwlItemService.changeMwlItemsStatus(); - + return this.response .set("Content-Type", "application/dicom+json") .status(200) @@ -33,7 +32,7 @@ class ChangeMwlItemStatusCountController extends Controller { * @param {import('express').Response} res */ module.exports = async function (req, res) { - let controller = new ChangeMwlItemStatusCountController(req, res); + let controller = new ChangeMwlItemStatusController(req, res); await controller.doPipeline(); }; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js new file mode 100644 index 00000000..4711b742 --- /dev/null +++ b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js @@ -0,0 +1,34 @@ +const _ = require("lodash"); +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); +const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); + +class ChangeFilteredMwlItemStatusService extends BaseQueryService { + constructor(req, res) { + super(req, res); + } + + async changeMwlItemsStatus() { + let { status } = this.request.params; + let mwlItems = await this.getMwlItems(); + if (mwlItems.length === 0) { + throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "Can not found any MWL item from query", 404); + } + + for (let mwlItem of mwlItems) { + _.set(mwlItem, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); + await mwlItem.save(); + } + + return mwlItems.length; + } + + async getMwlItems() { + let query = (await convertRequestQueryToMongoQuery(this.query)).$match; + return await MwlItemModel.find(query); + } +} + +module.exports.ChangeFilteredMwlItemStatusService = ChangeFilteredMwlItemStatusService; \ No newline at end of file diff --git a/api/dicom-web/mwl-rs.route.js b/api/dicom-web/mwl-rs.route.js index b8ae6ee6..06e0e0dd 100644 --- a/api/dicom-web/mwl-rs.route.js +++ b/api/dicom-web/mwl-rs.route.js @@ -131,4 +131,30 @@ router.post("/mwlitems/:studyUID/:spsID/status/:status", require("./controller/MWL-RS/change-mwlItem-status") ); +/** + * @openapi + * /dicom-web/mwlitems/status/{spsStatus}: + * post: + * tags: + * - MWL-RS + * description: > + * This transaction create or update a Modality WorkList item. + * requestBody: + * content: + * application/dicom+json: + * parameters: + * - $ref: "#/components/parameters/spsStatus" + * - $ref: "#/components/parameters/filter" + * responses: + * "200": + * description: change status of mwl items successfully + */ +router.post("/mwlitems/status/:status", + validateByJoi( + Joi.object({ + status: Joi.string().valid("SCHEDULED", "ARRIVED", "READY", "STARTED", "DEPARTED", "CANCELED", "DISCONTINUED", "COMPLETED").required() + }), "params", {allowUnknown: false}), + require("./controller/MWL-RS/change-filtered-mwlItem-status") +); + module.exports = router; \ No newline at end of file From e51fedbb9fbaac00f094bd001f763edf385b5f01 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 17:30:34 +0800 Subject: [PATCH 229/365] chore: add `api` modules alias --- config/jsconfig.mongodb.json | 3 ++- config/jsconfig.sql.json | 3 ++- config/modula-alias/mongodb/package.json | 3 ++- config/modula-alias/sql/package.json | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/config/jsconfig.mongodb.json b/config/jsconfig.mongodb.json index 970078b7..19e8ed22 100644 --- a/config/jsconfig.mongodb.json +++ b/config/jsconfig.mongodb.json @@ -24,7 +24,8 @@ "@dimse-study-query-task": ["./dimse/studyQueryTask.js"], "@dimse-series-query-task": ["./dimse/seriesQueryTask.js"], "@dimse-instance-query-task": ["./dimse/instanceQueryTask.js"], - "@dimse-utils": ["./dimse/utils.js"] + "@dimse-utils": ["./dimse/utils.js"], + "@api/*": ["./api/*"] } }, "exclude": [ diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index df2bc8ca..9b614b17 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -25,7 +25,8 @@ "@dimse-study-query-task": ["./dimse-sql/studyQueryTask.js"], "@dimse-series-query-task": ["./dimse-sql/seriesQueryTask.js"], "@dimse-instance-query-task": ["./dimse-sql/instanceQueryTask.js"], - "@dimse-utils": ["./dimse-sql/utils.js"] + "@dimse-utils": ["./dimse-sql/utils.js"], + "@api/*": ["./api-sql/*"] } }, "exclude": [ diff --git a/config/modula-alias/mongodb/package.json b/config/modula-alias/mongodb/package.json index 8a74ded6..6169e1a7 100644 --- a/config/modula-alias/mongodb/package.json +++ b/config/modula-alias/mongodb/package.json @@ -25,6 +25,7 @@ "@dimse-series-query-task": "../../../dimse/seriesQueryTask.js", "@dimse-instance-query-task": "../../../dimse/instanceQueryTask.js", "@dimse-utils": "../../../dimse/utils.js", - "@dimse": "../../../dimse" + "@dimse": "../../../dimse", + "@api": "../../../api" } } \ No newline at end of file diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index e30f7f83..9ea42751 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -25,6 +25,7 @@ "@dimse-series-query-task": "../../../dimse-sql/seriesQueryTask.js", "@dimse-instance-query-task": "../../../dimse-sql/instanceQueryTask.js", "@dimse-utils": "../../../dimse-sql/utils.js", - "@dimse": "../../../dimse-sql" + "@dimse": "../../../dimse-sql", + "@api": "../../../api-sql" } } \ No newline at end of file From e375ea5fca625d77617bad1a8bed7a293d73bf17 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 17:31:01 +0800 Subject: [PATCH 230/365] feat(sql): implement cancel workitem --- .../UPS-RS/service/cancel.service.js | 26 +++++++++++++++++++ api/dicom-web/controller/UPS-RS/cancel.js | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js diff --git a/api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js b/api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js new file mode 100644 index 00000000..e22027b9 --- /dev/null +++ b/api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -0,0 +1,26 @@ +const _ = require("lodash"); +const { WorkItemModel } = require("@models/sql/models/workitems.model"); +const { CancelWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/cancel.service"); + +class SqlCancelWorkItemService extends CancelWorkItemService { + + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + */ + constructor(req, res) { + super(req, res); + this.upsInstanceUID = this.request.params.workItem; + this.workItem = null; + this.requestWorkItem = /** @type {Object[]} */(this.request.body).pop(); + } + + async initWorkItem() { + this.workItem = await WorkItemModel.findOneWorkItemDicomJsonModel(this.upsInstanceUID); + } + +} + + +module.exports.CancelWorkItemService = SqlCancelWorkItemService; \ No newline at end of file diff --git a/api/dicom-web/controller/UPS-RS/cancel.js b/api/dicom-web/controller/UPS-RS/cancel.js index d8a6bbc4..a0da82f9 100644 --- a/api/dicom-web/controller/UPS-RS/cancel.js +++ b/api/dicom-web/controller/UPS-RS/cancel.js @@ -6,7 +6,7 @@ const { CancelWorkItemService -} = require("./service/cancel.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/cancel.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); From 0762cdbbdec7e24c76a4307f22da2f2a1911af50 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 19:11:49 +0800 Subject: [PATCH 231/365] feat(sql): change association between subscription and work item - Work items will be subscribed by multiple subscriptions, so we need to change association - to `WorkItemModel.hasMany(UpsSubscriptionModel)` --- .../UPS-RS/service/base-workItem.service.js | 17 ++++++----------- .../UPS-RS/service/subscribe.service.js | 11 +++++------ models/sql/init.js | 2 +- models/sql/models/workitems.model.js | 4 ---- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 6514abd4..915915b1 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -60,18 +60,13 @@ class SqlBaseWorkItemService extends BaseWorkItemService { return hitGlobalSubscriptions; } + /** + * + * @param {DicomJsonModel} workItem + * @returns + */ async getHitSubscriptions(workItem) { - let hitSubscriptions = await UpsSubscriptionModel.findAll({ - include: [ - { - model: WorkItemModel, - where: { - upsInstanceUID: workItem.getString("00080018") - }, - required: true - } - ] - }); + let hitSubscriptions = await workItem.dicomJson.getUpsSubscriptions(); return hitSubscriptions; } diff --git a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js index 205cd35b..c5e80c04 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -60,21 +60,20 @@ class SqlSubscribeService extends SubscribeService { await this.updateWorkItemSubscription(workItem, subscribed); if (!subscription) { // Create - let subscriptionObj = UpsSubscriptionModel.build({ + let subscriptionObj = await UpsSubscriptionModel.create({ aeTitle: this.subscriberAeTitle, isDeletionLock: this.deletionLock, subscribed: subscribed }); - await subscriptionObj.addUPSWorkItem(workItem); - let createdSubscription = await subscriptionObj.save(); - return createdSubscription; + await workItem.addUpsSubscription(subscriptionObj); + return subscriptionObj; } else { // Update subscription.isDeletionLock = this.deletionLock; subscription.subscribed = subscribed; - if (!await subscription.hasUPSWorkItem(workItem)) { - subscription.addUPSWorkItem(workItem); + if (!await workItem.hasUpsSubscription(subscription)) { + workItem.addUpsSubscription(subscription); } let updatedSubscription = await subscription.save(); return updatedSubscription; diff --git a/models/sql/init.js b/models/sql/init.js index 61bdc554..6035c3f2 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -167,7 +167,7 @@ async function init() { as: dictionary.tag["00404037"] }); - UpsSubscriptionModel.hasMany(WorkItemModel); + WorkItemModel.hasMany(UpsSubscriptionModel); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({ diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index a88a7971..94347b1d 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -114,10 +114,6 @@ const WorkItemSchema = { type: DataTypes.INTEGER, defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED }, - UpsSubscriptionId: { - type: DataTypes.INTEGER, - allowNull: true - }, //#region patient level "x00100020": { type: vrTypeMapping.LO, From 3d81948c7b48c90f90c91e3fe682c6a01f50ed49 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 20:06:45 +0800 Subject: [PATCH 232/365] refactor(mongodb): extract method to `triggerUpdateWorkItemEvent` --- .../controller/UPS-RS/service/update-workItem.service.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index a4f0119a..e028d3b2 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -58,10 +58,14 @@ class UpdateWorkItemService extends BaseWorkItemService { new: true }); - let updateWorkItemDicomJson = new DicomJsonModel(updatedWorkItem); + this.triggerUpdateWorkItemEvent(updatedWorkItem); + } + + async triggerUpdateWorkItemEvent(workItem) { + let updateWorkItemDicomJson = new DicomJsonModel(workItem); let hitSubscriptions = await this.getHitSubscriptions(updateWorkItemDicomJson); if (hitSubscriptions.length === 0) { - return updatedWorkItem; + return workItem; } let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); From 583dbcf23ac261952dacc8d03b8f13018ea15a52 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 20:43:33 +0800 Subject: [PATCH 233/365] feat: implement trigger update work item event --- .../controller/UPS-RS/service/update-workItem.service.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js index f9153f22..105b2df7 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -4,7 +4,7 @@ const { DicomJsonModel } = require("@dicom-json-model"); const { UpdateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/update-workItem.service"); const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); const { set, get } = require("lodash"); -const { CreateWorkItemService } = require("./create-workItem.service"); +const { PatientModel } = require("@models/sql/models/patient.model"); class SqlUpdateWorkItemService extends UpdateWorkItemService { constructor(req, res) { @@ -18,11 +18,12 @@ class SqlUpdateWorkItemService extends UpdateWorkItemService { await this.checkRequestUpsIsValid(); this.adjustRequestWorkItem(); - let createService = new CreateWorkItemService(this.request, this.response); - let patient = await createService.findOneOrCreatePatient(); + let patient = await PatientModel.updateOrCreatePatient(this.requestWorkItem.dicomJson); let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); let savedWorkItem = await workItem.save(); - //TODO: update UPS event + + this.workItem = new DicomJsonModel(workItem.json); + this.triggerUpdateWorkItemEvent(savedWorkItem); } /** From 46e95e9b7e247c3b62be7eaa0b30eb9df435bb51 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 21:59:43 +0800 Subject: [PATCH 234/365] feat(sql): add referenced request sequence of ups work item --- models/sql/init.js | 5 ++ .../sql/models/upsRequestAttributes.model.js | 41 ++++++++++++++ models/sql/models/workitems.model.js | 3 - models/sql/po/upsWorkItem.po.js | 56 +++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 models/sql/models/upsRequestAttributes.model.js diff --git a/models/sql/init.js b/models/sql/init.js index 6035c3f2..17c7791f 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -14,6 +14,7 @@ const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model const { WorkItemModel } = require("./models/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UpsSubscriptionModel } = require("./models/upsSubscription.model"); +const { UpsRequestAttributesModel } = require("./models/upsRequestAttributes.model"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -168,6 +169,10 @@ async function init() { }); WorkItemModel.hasMany(UpsSubscriptionModel); + WorkItemModel.hasOne(UpsRequestAttributesModel, { + foreignKey: "upsInstanceUID", + sourceKey: "upsInstanceUID" + }); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({ diff --git a/models/sql/models/upsRequestAttributes.model.js b/models/sql/models/upsRequestAttributes.model.js new file mode 100644 index 00000000..da0795cb --- /dev/null +++ b/models/sql/models/upsRequestAttributes.model.js @@ -0,0 +1,41 @@ +const { DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); +const _ = require("lodash"); + +class UpsRequestAttributesModel extends Model {} + +UpsRequestAttributesModel.init({ + "upsInstanceUID": { + type: DataTypes.STRING, + allowNull: false + }, + "x00080050": { + type: vrTypeMapping.SH + }, + "x00080051_x00400031": { + type: vrTypeMapping.UT + }, + "x00080051_x00400032": { + type: vrTypeMapping.UT + }, + "x00080051_x00400033": { + type: vrTypeMapping.CS + }, + "x00321033": { + type: vrTypeMapping.LO + }, + "x00401001": { + type: vrTypeMapping.SH + }, + "x0020000D": { + type: vrTypeMapping.UI + } +}, { + sequelize: sequelizeInstance, + modelName: "UpsRequestAttributesModel", + tableName: "UpsRequestAttributesModel", + freezeTableName: true +}); + +module.exports.UpsRequestAttributesModel = UpsRequestAttributesModel; \ No newline at end of file diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js index 94347b1d..c462b0a8 100644 --- a/models/sql/models/workitems.model.js +++ b/models/sql/models/workitems.model.js @@ -191,9 +191,6 @@ const WorkItemSchema = { "json": { type: vrTypeMapping.JSON } - //TODO: Referenced Request Sequence - // You should create new Model for Referenced Request Sequence (0040,A370) - // model name should be called UPSRequest }; WorkItemModel.init(WorkItemSchema, { diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index 65e02e73..3607614f 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -6,6 +6,7 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { vrValueTransform } = require("./utils"); const { WorkItemModel } = require("../models/workitems.model"); const { DicomCodeModel } = require("../models/dicomCode.model"); +const { UpsRequestAttributesModel } = require("../models/upsRequestAttributes.model"); class UpsWorkItemPersistentObject { @@ -77,6 +78,9 @@ class UpsWorkItemPersistentObject { await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledWorkitemCodeSequence); await this.setGeneralCode(upsWorkItem, dictionary.keyword.InstitutionCodeSequence); await this.setHumanPerformerName(upsWorkItem); + + let requestAttributeDAO = new UpsWorkItemRequestAttributeDAO(this.upsInstanceUID, this.json); + await requestAttributeDAO.update(upsWorkItem); await upsWorkItem.save(); if (!created) { @@ -149,4 +153,56 @@ class UpsWorkItemPersistentObject { } } +class UpsWorkItemRequestAttributeDAO { + constructor(upsInstanceUID, dicomJson) { + this.dicomJson = dicomJson; + this.dicomJsonObj = new BaseDicomJson(this.dicomJson); + this.upsInstanceUID = upsInstanceUID; + } + + async getRequestAttribute() { + let requestAttribute = this.dicomJsonObj.getValue(dictionary.keyword.ReferencedRequestSequence); + if (requestAttribute) { + return { + upsInstanceUID: this.upsInstanceUID, + x00080050: get(requestAttribute, "00080050.Value.0"), + x00080051_x00400031: get(requestAttribute, "00080051.Value.0.00400031.Value.0"), + x00080051_x00400032: get(requestAttribute, "00080051.Value.0.00400032.Value.0"), + x00080051_x00400033: get(requestAttribute, "00080051.Value.0.00400033.Value.0"), + x00321033: get(requestAttribute, "00321033.Value.0"), + x00401001: get(requestAttribute, "00401001.Value.0"), + x0020000D: get(requestAttribute, "0020000D.Value.0") + }; + } + + return undefined; + } + + + /** + * + * @param {WorkItemModel} workItem + */ + async update(workItem) { + let requestAttributes = await this.getRequestAttribute(); + if (requestAttributes) { + if (await workItem.getUpsRequestAttributesModel()) { + await UpsRequestAttributesModel.update(requestAttributes, { + where: { + upsInstanceUID: workItem.upsInstanceUID + } + }); + } else { + await workItem.createUpsRequestAttributesModel(requestAttributes); + } + } else { + await UpsRequestAttributesModel.destroy({ + where: { + upsInstanceUID: workItem.upsInstanceUID + } + }); + } + } +} + module.exports.UpsWorkItemPersistentObject = UpsWorkItemPersistentObject; \ No newline at end of file From b4e347fa9cc69bf402bcbf95e3f27e24d8ea11e4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 23:22:23 +0800 Subject: [PATCH 235/365] fix: incorrect require path --- models/DICOM/audit/auditMessageFactory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/DICOM/audit/auditMessageFactory.js b/models/DICOM/audit/auditMessageFactory.js index 09852421..87612832 100644 --- a/models/DICOM/audit/auditMessageFactory.js +++ b/models/DICOM/audit/auditMessageFactory.js @@ -351,7 +351,7 @@ class AuditMessageFactory { getInstanceModel() { if (raccoonConfig.serverConfig.dbType === "sql") { - const sequelizeInstance = require("@models/mysql/instance"); + const sequelizeInstance = require("@models/sql/instance"); return sequelizeInstance.model("Instance"); } else { const mongoose = require("mongoose"); From a8a71fbff0f0fc58d6b718acccbcdb83c2298612 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 23:32:46 +0800 Subject: [PATCH 236/365] fix(sql): retrieve workitem is not working properly # problems - always return first item, that mean ups instance query is not working # solutions - missing include query of upsInstanceUID in path - add it --- .../controller/UPS-RS/service/query/upsQueryBuilder.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js b/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js index 94168cda..8fa740ca 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js @@ -3,6 +3,7 @@ const { PatientQueryBuilder } = require("../../../QIDO-RS/service/patientQueryBu const { BaseQueryBuilder } = require("../../../QIDO-RS/service/querybuilder"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { DicomCodeQueryBuilder } = require("../../../QIDO-RS/service/dicomCodeQueryBuilder"); +const { get } = require("lodash"); class UpsQueryBuilder extends BaseQueryBuilder { @@ -25,6 +26,13 @@ class UpsQueryBuilder extends BaseQueryBuilder { this.createCodeQueries(dictionary.keyword.ScheduledWorkitemCodeSequence); this.createScheduledHumanPerformersSequenceQueries(); this.createIssuerOfAdmissionIdSequenceQueries(); + + let upsInstanceUIDInParams = get(this.queryOptions.requestParams, "workItem"); + if (upsInstanceUIDInParams) { + this.query = { + upsInstanceUID: upsInstanceUIDInParams + }; + } } /** From a5b5843a5afbe5971f62a862bc58e9520530d41e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 16 Dec 2023 23:36:42 +0800 Subject: [PATCH 237/365] fix(sql): trigger event of update work item broken # Problems - Received data is not completed # Solutions - The mongodb is json based so it can always use DicomJsonModel, but sql not - create new triggerUpdateWorkItemEvent for sql version --- .../service/change-workItem-state.service.js | 3 +- .../UPS-RS/service/update-workItem.service.js | 34 ++++++++++++++++++- models/sql/po/upsWorkItem.po.js | 7 +++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index 43f159f9..3895cdb9 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -1,4 +1,5 @@ const { WorkItemModel } = require("@dbModels/workitems.model"); +const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); const { ChangeWorkItemStateService } = require("@root/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service"); const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); @@ -42,7 +43,7 @@ class SqlChangeWorkItemStateService extends ChangeWorkItemStateService { async triggerUpsChangeStateEvent(updatedWorkItem) { let updatedWorkItemDicomJsonModelObj = updatedWorkItem.toDicomJsonModel(); - let hitSubscriptions = await this.getHitSubscriptions(updatedWorkItemDicomJsonModelObj); + let hitSubscriptions = await this.getHitSubscriptions(new DicomJsonModel(updatedWorkItem)); if (hitSubscriptions.length === 0) return; diff --git a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 105b2df7..f89389dc 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -5,6 +5,8 @@ const { UpdateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); const { set, get } = require("lodash"); const { PatientModel } = require("@models/sql/models/patient.model"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); class SqlUpdateWorkItemService extends UpdateWorkItemService { constructor(req, res) { @@ -22,7 +24,6 @@ class SqlUpdateWorkItemService extends UpdateWorkItemService { let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); let savedWorkItem = await workItem.save(); - this.workItem = new DicomJsonModel(workItem.json); this.triggerUpdateWorkItemEvent(savedWorkItem); } @@ -36,6 +37,37 @@ class SqlUpdateWorkItemService extends UpdateWorkItemService { set(this.requestWorkItem.dicomJson, originalValueOfNotAllowedAttr); } } + + /** + * + * @param {WorkItemModel} workItem + * @returns + */ + async triggerUpdateWorkItemEvent(workItem) { + let updateWorkItemDicomJson = new DicomJsonModel(workItem); + let hitSubscriptions = await this.getHitSubscriptions(updateWorkItemDicomJson); + if (hitSubscriptions.length === 0) { + return workItem; + } + let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); + + //Each time the SCP changes the Input Readiness State (0040,4041) Attribute for a UPS instance, the SCP shall send a UPS State Report Event to subscribed SCUs. + let modifiedInputReadLineState = this.requestWorkItem.getString(`${dictionary.keyword.InputReadinessState}`); + let originalInputReadLineState = this.workItem.getString(`${dictionary.keyword.InputReadinessState}`); + if (modifiedInputReadLineState && modifiedInputReadLineState !== originalInputReadLineState) { + this.addUpsEvent( + UPS_EVENT_TYPE.StateReport, + this.workItem.dicomJson.upsInstanceUID, + this.stateReportOf(workItem.toDicomJsonModel()), + hitSubscriptionAeTitleArray + ); + } + + this.addProgressInfoUpdatedEvent(workItem.toDicomJsonModel(), hitSubscriptionAeTitleArray); + this.addAssignedEvents(workItem.toDicomJsonModel(), hitSubscriptionAeTitleArray); + + this.triggerUpsEvents(); + } } module.exports.UpdateWorkItemService = SqlUpdateWorkItemService; \ No newline at end of file diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index 3607614f..6999b5a0 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -81,11 +81,16 @@ class UpsWorkItemPersistentObject { let requestAttributeDAO = new UpsWorkItemRequestAttributeDAO(this.upsInstanceUID, this.json); await requestAttributeDAO.update(upsWorkItem); - await upsWorkItem.save(); if (!created) { await this.removeAllAssociationItems(tempUpsWorkItem); + upsWorkItem.json = { + ...upsWorkItem.json, + ...this.json + }; + upsWorkItem.changed("json", true); } + await upsWorkItem.save(); return upsWorkItem; } From 070689ba95e59d566758a0e55717a81aaec93882 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 17 Dec 2023 00:14:36 +0800 Subject: [PATCH 238/365] fix(sql): missing ignore not allowed attr in json field when update --- models/sql/po/upsWorkItem.po.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js index 6999b5a0..928f268f 100644 --- a/models/sql/po/upsWorkItem.po.js +++ b/models/sql/po/upsWorkItem.po.js @@ -1,4 +1,4 @@ -const { get, set, cloneDeep } = require("lodash"); +const { get, set, cloneDeep, unset } = require("lodash"); const { PersonNameModel } = require("../models/personName.model"); const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); @@ -7,6 +7,7 @@ const { vrValueTransform } = require("./utils"); const { WorkItemModel } = require("../models/workitems.model"); const { DicomCodeModel } = require("../models/dicomCode.model"); const { UpsRequestAttributesModel } = require("../models/upsRequestAttributes.model"); +const { UpdateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/update-workItem.service"); class UpsWorkItemPersistentObject { @@ -84,6 +85,7 @@ class UpsWorkItemPersistentObject { if (!created) { await this.removeAllAssociationItems(tempUpsWorkItem); + this.adjustUpdateWorkItem(); upsWorkItem.json = { ...upsWorkItem.json, ...this.json @@ -156,6 +158,13 @@ class UpsWorkItemPersistentObject { x00380014_x00400033: this.admissionUniversalEntityIdType }; } + + adjustUpdateWorkItem() { + for (let i = 0; i < UpdateWorkItemService.notAllowedAttributes.length; i++) { + let notAllowedAttr = UpdateWorkItemService.notAllowedAttributes[i]; + unset(this.json, notAllowedAttr); + } + } } class UpsWorkItemRequestAttributeDAO { From 13c6619b63ba4661af7f51d4aab9fdb23493150a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 17 Dec 2023 10:20:47 +0800 Subject: [PATCH 239/365] fix(mongodb): not save audit message into db - missing adaptor for mongodb in `getAuditMessageModel` --- models/DICOM/audit/auditManager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/models/DICOM/audit/auditManager.js b/models/DICOM/audit/auditManager.js index a95bf4c4..d50fbcae 100644 --- a/models/DICOM/audit/auditManager.js +++ b/models/DICOM/audit/auditManager.js @@ -4,6 +4,8 @@ const { AuditMessageFactory } = require("./auditMessageFactory"); const { EventType } = require("./eventType"); const { AuditMessageModel } = require("@models/db/auditMessage.model"); const { AuditMessageModelLoggerDbImpl } = require("@models/db/auditMessage.loggerImpl"); +const AuditMessageModelMongodbDbImpl = require("@models/mongodb/models/auditMessage"); +const { raccoonConfig } = require("@root/config-class"); /** * @typedef AuditMessageModel @@ -161,7 +163,9 @@ class AuditManager { } static getAuditMessageModel() { - return new AuditMessageModel(new AuditMessageModelLoggerDbImpl()); + if (raccoonConfig.serverConfig.dbType === "sql") + return new AuditMessageModel(new AuditMessageModelLoggerDbImpl()); + return new AuditMessageModel(AuditMessageModelMongodbDbImpl); } } From ff2353cd4ef97ef2f47fe08df8f3a07e9851b96d Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 17 Dec 2023 15:16:29 +0800 Subject: [PATCH 240/365] fix(sql): incorrect create event of state report json # Problems - 00404041 Input Readiness State - 00741000 Procedure Step State above is empty that is not correct # Solutions - The input is incorrect dicom json model, using workItem.toDicomJsonModel() instead --- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 564030a1..9553539c 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -92,7 +92,7 @@ class CreateWorkItemService extends BaseWorkItemService { if (hitSubscriptions) { let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItemDicomJson), hitSubscriptionAeTitleArray); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItem.toDicomJsonModel()), hitSubscriptionAeTitleArray); let assignedEventInformationArray = await this.getAssignedEventInformationArray( workItemDicomJson, _.get(workItemDicomJson.dicomJson, `${dictionary.keyword.ScheduledStationNameCodeSequence}`, false), From b63ba6f249a89b94518a7b472bf4e6bc8dc3b92c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 20 Dec 2023 10:09:35 +0800 Subject: [PATCH 241/365] feat(sql): add mwl item model --- models/sql/models/mwlItems.model.js | 161 ++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 models/sql/models/mwlItems.model.js diff --git a/models/sql/models/mwlItems.model.js b/models/sql/models/mwlItems.model.js new file mode 100644 index 00000000..f6b9cb11 --- /dev/null +++ b/models/sql/models/mwlItems.model.js @@ -0,0 +1,161 @@ +const { Sequelize, DataTypes, Model } = require("sequelize"); +const sequelizeInstance = require("@models/sql/instance"); +const { vrTypeMapping } = require("../vrTypeMapping"); +const { raccoonConfig } = require("@root/config-class"); +const { DicomJsonModel } = require("../dicom-json-model"); + +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + +class MwlItemModel extends Model { + async getAttributes() { + let obj = this.toJSON(); + let jsonStr = JSON.stringify(obj.json); + return await Common.getAttributesFromJsonString(jsonStr); + } + + toDicomJsonModel() { + return new DicomJsonModel(this.json); + } + static async getDicomJson (queryOptions) { + // TODO: implement + } +}; + +/** @type { import("sequelize").ModelAttributes } */ +const MwlItemSchema = { + // 0020000D + study_instance_uid: { + type: vrTypeMapping.UI, + allowNull: false, + unique: true + }, + // 00100010 + patient_id: { + type: vrTypeMapping.LO, + allowNull: false + }, + // 00800050 + accession_number: { + type: vrTypeMapping.SH + }, + // 00800051.00400031 + accno_local_id: { + type: vrTypeMapping.UT + }, + // 00800051.00400032 + accno_universal_id: { + type: vrTypeMapping.UT + }, + // 00800051.00400033 + accno_universal_id_type: { + type: vrTypeMapping.CS + }, + // 00401001 + requested_procedure_id: { + type: vrTypeMapping.SH + }, + // 00380010 + admission_id: { + type: vrTypeMapping.LO + }, + // 00380014.00400031 + issuer_admission_local_id: { + type: vrTypeMapping.UT + }, + // 00380014.00400032 + issuer_admission_universal_id: { + type: vrTypeMapping.UT + }, + // 00380014.00400033 + issuer_admission_universal_id_type: { + type: vrTypeMapping.CS + }, + // TODO Scheduled Procedure Step Sequence + // 00400001 + station_ae_title: { + type: vrTypeMapping.AE + }, + // 00400010 + station_name: { + type: vrTypeMapping.SH + }, + // 00400002 + start_date: { + type: vrTypeMapping.DA + }, + // 00400004 + end_date: { + type: vrTypeMapping.DA + }, + // 00400003 + start_time: { + type: vrTypeMapping.DT + }, + // 00400005 + end_time: { + type: vrTypeMapping.DT + }, + // 00400006 + physician_name: { + //* must reference to PersonName model + type: vrTypeMapping.PN + }, + // 00400011 + procedure_step_location: { + type: vrTypeMapping.SH + }, + // 00400007 + description: { + type: vrTypeMapping.LO + }, + // 00400008 + protocol_code: { + // reference to dicom code model + type: DataTypes.INTEGER + }, + // 00080080 + institution_name: { + type: vrTypeMapping.LO + }, + // 00081040 + institution_department_name: { + type: vrTypeMapping.LO + }, + // Reference to Dicom Code Model + // 00081041 + institution_department_type_code: { + type: DataTypes.INTEGER + }, + // Reference to Dicom Code Model + // 00080082 + institution_code: { + type: DataTypes.INTEGER + }, + sps_id: { + type: vrTypeMapping.SH + }, + sps_status: { + type: vrTypeMapping.CS + }, + modality: { + type: vrTypeMapping.CS + }, + json: { + type: vrTypeMapping.JSON + } +}; + + +MwlItemModel.init(MwlItemSchema, { + sequelize: sequelizeInstance, + modelName: "mwl_item", + tableName: "mwl_item", + freezeTableName: true +}); + +module.exports.MwlItemModel = MwlItemModel; +module.exports.MwlItemSchema = MwlItemSchema; From e3d951ca8ad6c4b82d624de9cfd29a9f88b7e7a7 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 20 Dec 2023 10:09:42 +0800 Subject: [PATCH 242/365] feat(sql): association for mwl item --- models/sql/init.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/models/sql/init.js b/models/sql/init.js index 17c7791f..7cfc9b18 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -15,6 +15,7 @@ const { WorkItemModel } = require("./models/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UpsSubscriptionModel } = require("./models/upsSubscription.model"); const { UpsRequestAttributesModel } = require("./models/upsRequestAttributes.model"); +const { MwlItemModel } = require("./models/mwlItems.model"); async function initDatabasePostgres() { const { Client } = require("pg"); @@ -173,6 +174,28 @@ async function init() { foreignKey: "upsInstanceUID", sourceKey: "upsInstanceUID" }); + + MwlItemModel.belongsTo(PatientModel, { + targetKey: "x00100020", + foreignKey: "patient_id" + }); + + MwlItemModel.belongsTo(DicomCodeModel, { + foreignKey: "protocol_code", + as: dictionary.tag["00400008"] + }); + MwlItemModel.belongsTo(DicomCodeModel, { + foreignKey: "institution_department_type_code", + as: dictionary.tag["00081041"] + }); + MwlItemModel.belongsTo(DicomCodeModel, { + foreignKey: "institution_code", + as: dictionary.tag["00080082"] + }); + MwlItemModel.belongsTo(PersonNameModel, { + foreignKey: "physician_name", + as: dictionary.tag["00400006"] + }); //TODO: 設計完畢後要將 force 刪除 await sequelizeInstance.sync({ From 50429e66261a294474ba2b1acf75acf18ddf2af3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 20 Dec 2023 10:14:20 +0800 Subject: [PATCH 243/365] fix(mongodb): query object incorrect in `checkPatientExist` --- .../controller/MWL-RS/service/create-mwlitem.service.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js index 44ca6068..ae7264ec 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js @@ -90,9 +90,7 @@ class CreateMwlItemService { async checkPatientExist() { let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); let patientCount = await PatientModel.count({ - where: { - x00100020: patientID - } + patientID }); if (patientCount <= 0) { throw new DicomWebServiceError( From 6f345e34d292683dcf8942040d2d7f6fc685f36f Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 20 Dec 2023 10:38:38 +0800 Subject: [PATCH 244/365] feat(sql): implement create mwl item --- .../MWL-RS/service/create-mwlItem.service.js | 40 +++++ .../controller/MWL-RS/create-mwlItem.js | 2 +- config/jsconfig.mongodb.json | 2 + config/jsconfig.sql.json | 1 + config/modula-alias/mongodb/package.json | 1 + config/modula-alias/sql/package.json | 1 + models/sql/po/mwlItem.po.js | 162 ++++++++++++++++++ 7 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js create mode 100644 models/sql/po/mwlItem.po.js diff --git a/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js new file mode 100644 index 00000000..b9e30767 --- /dev/null +++ b/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js @@ -0,0 +1,40 @@ +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { PatientModel } = require("@models/sql/models/patient.model"); +const { CreateMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { MwlItemPersistentObject } = require("@models/sql/po/mwlItem.po"); +const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); + +class SqlCreateMwlItemService extends CreateMwlItemService { + constructor(req, res) { + super(req, res); + this.requestMwlItemDicomJsonModel = new DicomJsonModel(this.requestMwlItem[0]); + } + + async checkPatientExist() { + let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); + let patientCount = await PatientModel.count({ + where: { + x00100020: patientID + } + }); + if (patientCount <= 0) { + throw new DicomWebServiceError( + DicomWebStatusCodes.MissingAttribute, + `Patient[id=${patientID}] does not exists`, + 404 + ); + } + } + + async createOrUpdateMwl(mwlDicomJson) { + let studyInstanceUID = mwlDicomJson.getValue(dictionary.keyword.StudyInstanceUID); + let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); + let mwlItemPO = new MwlItemPersistentObject(mwlDicomJson.dicomJson, await PatientModel.findOne({ where: { x00100020: patientID } })); + let mwlItem = await mwlItemPO.save(); + this.apiLogger.logger.info(`create mwl item: ${studyInstanceUID}`); + return mwlItem.json; + } +} + +module.exports.CreateMwlItemService = SqlCreateMwlItemService; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/create-mwlItem.js b/api/dicom-web/controller/MWL-RS/create-mwlItem.js index f488b1db..aa387248 100644 --- a/api/dicom-web/controller/MWL-RS/create-mwlItem.js +++ b/api/dicom-web/controller/MWL-RS/create-mwlItem.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { CreateMwlItemService } = require("./service/create-mwlitem.service"); +const { CreateMwlItemService } = require("@mwl-service/create-mwlitem.service"); class CreateMwlItemController extends Controller { constructor(req, res) { diff --git a/config/jsconfig.mongodb.json b/config/jsconfig.mongodb.json index 19e8ed22..199b4dd8 100644 --- a/config/jsconfig.mongodb.json +++ b/config/jsconfig.mongodb.json @@ -19,6 +19,8 @@ "@delete-service": ["./api/dicom-web/controller/WADO-RS/deletion/service/delete.js"], "@rendered-service": ["./api/dicom-web/controller/WADO-RS/service/rendered.service.js"], "@thumbnail-service": ["./api/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], + "@ups-service/*": ["./api/dicom-web/controller/UPS-RS/service/*"], + "@mwl-service/*": ["./api/dicom-web/controller/MWL-RS/service/*"], "@dimse-query-builder": ["./dimse/queryBuilder.js"], "@dimse-patient-query-task": ["./dimse/patientQueryTask.js"], "@dimse-study-query-task": ["./dimse/studyQueryTask.js"], diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json index 9b614b17..c4d1a146 100644 --- a/config/jsconfig.sql.json +++ b/config/jsconfig.sql.json @@ -20,6 +20,7 @@ "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"], "@thumbnail-service": ["./api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], "@ups-service/*": ["./api-sql/dicom-web/controller/UPS-RS/service/*"], + "@mwl-service/*": ["./api-sql/dicom-web/controller/MWL-RS/service/*"], "@dimse-query-builder": ["./dimse-sql/queryBuilder.js"], "@dimse-patient-query-task": ["./dimse-sql/patientQueryTask.js"], "@dimse-study-query-task": ["./dimse-sql/studyQueryTask.js"], diff --git a/config/modula-alias/mongodb/package.json b/config/modula-alias/mongodb/package.json index 6169e1a7..1ccf6c60 100644 --- a/config/modula-alias/mongodb/package.json +++ b/config/modula-alias/mongodb/package.json @@ -19,6 +19,7 @@ "@rendered-service": "../../../api/dicom-web/controller/WADO-RS/service/rendered.service.js", "@thumbnail-service": "../../../api/dicom-web/controller/WADO-RS/service/thumbnail.service.js", "@ups-service": "../../../api/dicom-web/controller/UPS-RS/service", + "@mwl-service": "../../../api/dicom-web/controller/MWL-RS/service", "@dimse-query-builder": "../../../dimse/queryBuilder.js", "@dimse-patient-query-task": "../../../dimse/patientQueryTask.js", "@dimse-study-query-task": "../../../dimse/studyQueryTask.js", diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index 9ea42751..cf5845c7 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -19,6 +19,7 @@ "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js", "@thumbnail-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js", "@ups-service": "../../../api-sql/dicom-web/controller/UPS-RS/service", + "@mwl-service": "../../../api-sql/dicom-web/controller/MWL-RS/service", "@dimse-query-builder": "../../../dimse-sql/queryBuilder.js", "@dimse-patient-query-task": "../../../dimse-sql/patientQueryTask.js", "@dimse-study-query-task": "../../../dimse-sql/studyQueryTask.js", diff --git a/models/sql/po/mwlItem.po.js b/models/sql/po/mwlItem.po.js new file mode 100644 index 00000000..a0b05bcd --- /dev/null +++ b/models/sql/po/mwlItem.po.js @@ -0,0 +1,162 @@ +const { get, set, cloneDeep, unset } = require("lodash"); +const { PersonNameModel } = require("../models/personName.model"); +const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { vrValueTransform } = require("./utils"); +const { DicomCodeModel } = require("../models/dicomCode.model"); +const { MwlItemModel } = require("../models/mwlItems.model"); +const { Op } = require("sequelize"); + + +class MwlItemPersistentObject { + constructor(dicomJson, patient) { + + this.json = {}; + this.initJsonProperties(dicomJson); + + this.patient = patient; + + let dicomJsonObj = new BaseDicomJson(dicomJson); + this.study_instance_uid = dicomJsonObj.getValue("0020000D"); + this.accession_number = dicomJsonObj.getValue(dictionary.keyword.AccessionNumber); + this.accno_local_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAccessionNumberSequence}.Value.0.${dictionary.keyword.LocalNamespaceEntityID}`); + this.accno_universal_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAccessionNumberSequence}.Value.0.${dictionary.keyword.UniversalEntityID}`); + this.accno_universal_id_type = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAccessionNumberSequence}.Value.0.${dictionary.keyword.UniversalEntityIDType}`); + this.requested_procedure_id = dicomJsonObj.getValue(dictionary.keyword.RequestedProcedureID); + this.admission_id = dicomJsonObj.getValue(dictionary.keyword.AdmissionID); + this.issuer_admission_local_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.LocalNamespaceEntityID}`); + this.issuer_admission_universal_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.UniversalEntityID}`); + this.issuer_admission_universal_id_type = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.UniversalEntityIDType}`); + this.station_ae_title = dicomJsonObj.getValue(dictionary.keyword.StationAETitle); + this.station_name = dicomJsonObj.getValue(dictionary.keyword.StationName); + this.start_date = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepStartDate); + this.end_date = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepEndDate); + this.start_time = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepStartTime); + this.end_time = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepEndTime); + this.physician_name = dicomJsonObj.getValue(dictionary.keyword.ScheduledPerformingPhysicianName); + this.procedure_step_location = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepLocation); + this.description = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepDescription); + this.protocol_code = dicomJsonObj.getValue(dictionary.keyword.ScheduledProtocolCodeSequence); + this.institution_name = dicomJsonObj.getValue(dictionary.keyword.InstitutionName); + this.institution_department_name = dicomJsonObj.getValue(dictionary.keyword.InstitutionalDepartmentName); + this.institution_department_type_code = dicomJsonObj.getValue(dictionary.keyword.InstitutionalDepartmentTypeCodeSequence); + this.institution_code = dicomJsonObj.getValue(dictionary.keyword.InstitutionCodeSequence); + this.sps_id = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepID); + this.sps_status = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepStatus); + this.modality = dicomJsonObj.getValue(dictionary.keyword.Modality); + } + + initJsonProperties(dicomJson) { + Object.keys({ + ...tagsNeedStore.MWL, + ...tagsNeedStore.Patient + }).forEach(key => { + let value = get(dicomJson, key); + value ? set(this.json, key, value) : undefined; + }); + } + + async save() { + let mwlItemObj = MwlItemModel.build(this.getPersistentObject()); + mwlItemObj.patient_id = this.patient.x00100020; + let [mwlItem, created] = await MwlItemModel.findOrCreate({ + where: { + [Op.and]: [ + { + sps_id: this.sps_id + }, + { + study_instance_uid: this.study_instance_uid + } + ] + }, + defaults: mwlItemObj.toJSON() + }); + + await this.setGeneralCode(mwlItem, dictionary.keyword.InstitutionalDepartmentTypeCodeSequence); + await this.setGeneralCode(mwlItem, dictionary.keyword.InstitutionCodeSequence); + await this.setGeneralCode(mwlItem, dictionary.keyword.ScheduledProtocolCodeSequence); + await this.setPhysicianName(mwlItem); + + if (!created) { + await this.removeAllAssociationItems(mwlItem); + mwlItem.json = { + ...mwlItem.json, + ...this.json + }; + mwlItem.changed("json", true); + } + await mwlItem.save(); + + return mwlItem; + } + + async setGeneralCode(item, tag) { + if (this[`x${tag}`]) { + let code = await DicomCodeModel.create({ + "x00080100": get(this[`x${tag}`], "00080100.Value.0", undefined), + "x00080102": get(this[`x${tag}`], "00080102.Value.0", undefined), + "x00080103": get(this[`x${tag}`], "00080103.Value.0", undefined), + "x00080104": get(this[`x${tag}`], "00080104.Value.0", undefined) + }); + let keyword = dictionary.tag[tag]; + await item[`set${keyword}`](code); + } + } + + async setPhysicianName(mwlItem) { + if (this.physician_name) { + let nameOfPhysician = await PersonNameModel.createPersonName(this.physician_name); + await mwlItem[`set${dictionary.tag["00400006"]}`](nameOfPhysician); + } + } + + async removeAllAssociationItems(upsWorkItem) { + const associationItemsNames = [ + "InstitutionalDepartmentTypeCodeSequence", + "InstitutionCodeSequence", + "ScheduledProtocolCodeSequence", + "ScheduledPerformingPhysicianName" + ]; + + for (let i = 0; i < associationItemsNames.length; i++) { + let associationItemName = associationItemsNames[i]; + let associationItem = await upsWorkItem[`get${associationItemName}`](); + if (associationItem) { + await associationItem.destroy(); + } + } + } + + getPersistentObject() { + return { + json: this.json, + study_instance_uid: this.study_instance_uid, + accession_number: this.accession_number, + accno_local_id: this.accno_local_id, + accno_universal_id: this.accno_universal_id, + accno_universal_id_type: this.accno_universal_id_type, + requested_procedure_id: this.requested_procedure_id, + admission_id: this.admission_id, + issuer_admission_local_id: this.issuer_admission_local_id, + issuer_admission_universal_id: this.issuer_admission_universal_id, + issuer_admission_universal_id_type: this.issuer_admission_universal_id_type, + station_ae_title: this.station_ae_title, + station_name: this.station_name, + start_date: this.start_date, + end_date: this.end_date, + start_time: vrValueTransform.DT(this.start_time), + end_time: vrValueTransform.DT(this.end_time), + procedure_step_location: this.procedure_step_location, + description: this.description, + institution_name: this.institution_name, + institution_department_name: this.institution_department_name, + sps_id: this.sps_id, + sps_status: this.sps_status, + modality: this.modality + }; + } +} + +module.exports.MwlItemPersistentObject = MwlItemPersistentObject; \ No newline at end of file From 693f8de259846d3a1ec7aa80696dd4418eafbdfc Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 20 Dec 2023 10:40:35 +0800 Subject: [PATCH 245/365] fix: dicom json model should input object not array --- .../controller/MWL-RS/service/create-mwlitem.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js index ae7264ec..b546df2d 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js @@ -21,7 +21,7 @@ class CreateMwlItemService { this.response = res; this.requestMwlItem = /** @type {Object} */(this.request.body); /** @type {DicomJsonModel} */ - this.requestMwlItemDicomJsonModel = new DicomJsonModel(this.requestMwlItem); + this.requestMwlItemDicomJsonModel = new DicomJsonModel(this.requestMwlItem[0]); this.apiLogger = new ApiLogger(req, "Create Mwl Item Service"); this.apiLogger.addTokenValue(); } From 545a876642c3c4a859d1b8d020b246dbe570363a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 10:27:54 +0800 Subject: [PATCH 246/365] fix(sql): sps not store properly # Problems - sps is nested object, missing parent `ScheduledProcedureStepSequence` in getValue --- models/sql/models/mwlItems.model.js | 23 ++++++++++-------- models/sql/po/mwlItem.po.js | 36 +++++++++++++++-------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/models/sql/models/mwlItems.model.js b/models/sql/models/mwlItems.model.js index f6b9cb11..165ba1f4 100644 --- a/models/sql/models/mwlItems.model.js +++ b/models/sql/models/mwlItems.model.js @@ -75,44 +75,44 @@ const MwlItemSchema = { type: vrTypeMapping.CS }, // TODO Scheduled Procedure Step Sequence - // 00400001 + // 0040,0100.00400001 station_ae_title: { type: vrTypeMapping.AE }, - // 00400010 + // 0040,0100.00400010 station_name: { type: vrTypeMapping.SH }, - // 00400002 + // 0040,0100.00400002 start_date: { type: vrTypeMapping.DA }, - // 00400004 + // 0040,0100.00400004 end_date: { type: vrTypeMapping.DA }, - // 00400003 + // 0040,0100.00400003 start_time: { type: vrTypeMapping.DT }, - // 00400005 + // 0040,0100.00400005 end_time: { type: vrTypeMapping.DT }, - // 00400006 + // 0040,0100.00400006 physician_name: { //* must reference to PersonName model type: vrTypeMapping.PN }, - // 00400011 + // 0040,0100.00400011 procedure_step_location: { type: vrTypeMapping.SH }, - // 00400007 + // 0040,0100.00400007 description: { type: vrTypeMapping.LO }, - // 00400008 + // 0040,0100.00400008 protocol_code: { // reference to dicom code model type: DataTypes.INTEGER @@ -135,12 +135,15 @@ const MwlItemSchema = { institution_code: { type: DataTypes.INTEGER }, + // 00400100.00400009 sps_id: { type: vrTypeMapping.SH }, + // 00400100.00400020 sps_status: { type: vrTypeMapping.CS }, + // 00400100.00080060 modality: { type: vrTypeMapping.CS }, diff --git a/models/sql/po/mwlItem.po.js b/models/sql/po/mwlItem.po.js index a0b05bcd..d7cd97b1 100644 --- a/models/sql/po/mwlItem.po.js +++ b/models/sql/po/mwlItem.po.js @@ -28,23 +28,25 @@ class MwlItemPersistentObject { this.issuer_admission_local_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.LocalNamespaceEntityID}`); this.issuer_admission_universal_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.UniversalEntityID}`); this.issuer_admission_universal_id_type = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.UniversalEntityIDType}`); - this.station_ae_title = dicomJsonObj.getValue(dictionary.keyword.StationAETitle); - this.station_name = dicomJsonObj.getValue(dictionary.keyword.StationName); - this.start_date = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepStartDate); - this.end_date = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepEndDate); - this.start_time = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepStartTime); - this.end_time = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepEndTime); - this.physician_name = dicomJsonObj.getValue(dictionary.keyword.ScheduledPerformingPhysicianName); - this.procedure_step_location = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepLocation); - this.description = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepDescription); - this.protocol_code = dicomJsonObj.getValue(dictionary.keyword.ScheduledProtocolCodeSequence); - this.institution_name = dicomJsonObj.getValue(dictionary.keyword.InstitutionName); - this.institution_department_name = dicomJsonObj.getValue(dictionary.keyword.InstitutionalDepartmentName); - this.institution_department_type_code = dicomJsonObj.getValue(dictionary.keyword.InstitutionalDepartmentTypeCodeSequence); - this.institution_code = dicomJsonObj.getValue(dictionary.keyword.InstitutionCodeSequence); - this.sps_id = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepID); - this.sps_status = dicomJsonObj.getValue(dictionary.keyword.ScheduledProcedureStepStatus); - this.modality = dicomJsonObj.getValue(dictionary.keyword.Modality); + // #region sps (Scheduled Procedure Step) + this.station_ae_title = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.StationAETitle}`); + this.station_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.StationName}`); + this.start_date = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStartDate}`); + this.end_date = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepEndDate}`); + this.start_time = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStartTime}`); + this.end_time = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepEndTime}`); + this.physician_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledPerformingPhysicianName}`); + this.procedure_step_location = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepLocation}`); + this.description = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepDescription}`); + this.protocol_code = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProtocolCodeSequence}`); + this.institution_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionName}`); + this.institution_department_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionalDepartmentName}`); + this.institution_department_type_code = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionalDepartmentTypeCodeSequence}`); + this.institution_code = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionCodeSequence}`); + this.sps_id = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepID}`); + this.sps_status = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}`); + this.modality = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.Modality}`); + // #endregion } initJsonProperties(dicomJson) { From 0a9be041f42b7f8f9d709f531db7d358c454dbc2 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 17:23:31 +0800 Subject: [PATCH 247/365] fix(sql): can not update mwl item # Problems - The `removeAllAssociationItems` function remove saved mwlItem cause association issue # Solutions - We should remove old association from old item - Use `clone` to temp old mwl item and remove association of it --- models/sql/po/mwlItem.po.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/sql/po/mwlItem.po.js b/models/sql/po/mwlItem.po.js index d7cd97b1..24e953f5 100644 --- a/models/sql/po/mwlItem.po.js +++ b/models/sql/po/mwlItem.po.js @@ -75,14 +75,14 @@ class MwlItemPersistentObject { }, defaults: mwlItemObj.toJSON() }); + let tempClonedMwlItem = cloneDeep(mwlItem); await this.setGeneralCode(mwlItem, dictionary.keyword.InstitutionalDepartmentTypeCodeSequence); await this.setGeneralCode(mwlItem, dictionary.keyword.InstitutionCodeSequence); await this.setGeneralCode(mwlItem, dictionary.keyword.ScheduledProtocolCodeSequence); await this.setPhysicianName(mwlItem); - if (!created) { - await this.removeAllAssociationItems(mwlItem); + await this.removeAllAssociationItems(tempClonedMwlItem); mwlItem.json = { ...mwlItem.json, ...this.json From fca2bbb48925524818e3a2c2074b8e4485166b34 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 18:26:31 +0800 Subject: [PATCH 248/365] feat: support tag filed or human readable field for query builder --- .../QIDO-RS/service/querybuilder.js | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index a8e9cc63..6e21e833 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -98,15 +98,16 @@ class BaseQueryBuilder { * @returns */ getStringQuery(tag, value) { + let queryField = this.getQueryField(tag); if (value.includes("%") || value.includes("_")) { return { - [`x${tag}`]: { + [queryField]: { [Op.like]: value } }; } return { - [`x${tag}`]: value + [queryField]: value }; } @@ -132,22 +133,24 @@ class BaseQueryBuilder { } getStringArrayJsonQuery(tag, value) { + let queryField = this.getQueryField(tag); if (raccoonConfig.dbConfig.dialect === "postgres") { return { - [`x${tag}`]: { + [queryField]: { [Op.contains]: cast(JSON.stringify([value]), "jsonb") } }; } else { return { - [Op.or]: [Sequelize.where(Sequelize.fn("JSON_CONTAINS", Sequelize.col(`x${tag}`), `[${value}]`))] + [Op.or]: [Sequelize.where(Sequelize.fn("JSON_CONTAINS", Sequelize.col(queryField), `[${value}]`))] }; } } getNumberQuery(tag, value) { + let queryField = this.getQueryField(tag); return { - [`x${tag}`]: Number(value) + [queryField]: Number(value) }; } @@ -195,22 +198,23 @@ class BaseQueryBuilder { * @param {string} value */ getDateQuery(tag, value) { + let queryField = this.getQueryField(tag); let dashIndex = value.indexOf("-"); if (dashIndex === 0) { // -YYYYMMDD return { - [`x${tag}`]: { + [queryField]: { [Op.lte]: this.dateStringToSqlDateOnly(value.substring(1)) } }; } else if (dashIndex === value.length - 1) { // YYYYMMDD- return { - [`x${tag}`]: { + [queryField]: { [Op.gte]: this.dateStringToSqlDateOnly(value.substring(0, dashIndex)) } }; } else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD return { - [`x${tag}`]: { + [queryField]: { [Op.and]: [ { [Op.gte]: this.dateStringToSqlDateOnly(value.substring(0, dashIndex)) }, { [Op.lte]: this.dateStringToSqlDateOnly(value.substring(dashIndex + 1)) } @@ -219,7 +223,7 @@ class BaseQueryBuilder { }; } else { // YYYYMMDD return { - [`x${tag}`]: this.dateStringToSqlDateOnly(value) + [queryField]: this.dateStringToSqlDateOnly(value) }; } } @@ -230,22 +234,23 @@ class BaseQueryBuilder { * @param {string} value */ getTimeQuery(tag, value) { + let queryField = this.getQueryField(tag); let dashIndex = value.indexOf("-"); if (dashIndex === 0) { // -HHmmss return { - [`x${tag}`]: { + [queryField]: { [Op.lte]: this.timeStringToSqlTimeDecimal(value.substring(1)) } }; } else if (dashIndex === value.length - 1) { // HHmmss- return { - [`x${tag}`]: { + [queryField]: { [Op.gte]: this.timeStringToSqlTimeDecimal(value.substring(0, dashIndex)) } }; } else if (dashIndex > 0) { // HHmmss-HHmmss return { - [`x${tag}`]: { + [queryField]: { [Op.and]: [ { [Op.gte]: this.timeStringToSqlTimeDecimal(value.substring(0, dashIndex)) }, { [Op.lte]: this.timeStringToSqlTimeDecimal(value.substring(dashIndex + 1)) } @@ -254,7 +259,7 @@ class BaseQueryBuilder { }; } else { return { - [`x${tag}`]: this.timeStringToSqlTimeDecimal(value) + [queryField]: this.timeStringToSqlTimeDecimal(value) }; } } @@ -265,22 +270,23 @@ class BaseQueryBuilder { * @param {string} value */ getDateTimeQuery(tag, value) { + let queryField = this.getQueryField(tag); let dashIndex = value.indexOf("-"); if (dashIndex === 0) { // -YYYYMMDD return { - [`x${tag}`]: { + [queryField]: { [Op.lte]: this.dateTimeStringToSqlDateTime(value.substring(1)) } }; } else if (dashIndex === value.length - 1) { // YYYYMMDD- return { - [`x${tag}`]: { + [queryField]: { [Op.gte]: this.dateTimeStringToSqlDateTime(value.substring(0, dashIndex)) } }; } else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD return { - [`x${tag}`]: { + [queryField]: { [Op.and]: [ { [Op.gte]: this.dateTimeStringToSqlDateTime(value.substring(0, dashIndex)) }, { [Op.lte]: this.dateTimeStringToSqlDateTime(value.substring(dashIndex + 1)) } @@ -289,7 +295,7 @@ class BaseQueryBuilder { }; } else { // YYYYMMDD return { - [`x${tag}`]: this.dateTimeStringToSqlDateTime(value) + [queryField]: this.dateTimeStringToSqlDateTime(value) }; } } @@ -367,6 +373,15 @@ class BaseQueryBuilder { return Array.isArray(a) ? [...a, ...b] : { ...a, ...b }; }); } + + /** + * + * @param {string} tag + * @returns + */ + getQueryField(tag) { + return /^[0-9a-zA-Z]{8}$/.test(tag) ? `x${tag}` : tag; + } } class StudyQueryBuilder extends BaseQueryBuilder { From fa81ba6db3d32297c79bc2b3bbeab24cb3285efb Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 18:27:38 +0800 Subject: [PATCH 249/365] feat(sql): implement search for MWL item --- .../MWL-RS/service/get-mwlItem.service.js | 36 +++ .../MWL-RS/service/query/mwlQueryBuilder.js | 227 ++++++++++++++++++ .../UPS-RS/service/get-workItem.service.js | 14 +- .../controller/MWL-RS/get-mwlItem.js | 2 +- models/sql/models/mwlItems.model.js | 15 +- 5 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js create mode 100644 api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js diff --git a/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js new file mode 100644 index 00000000..de0ecc40 --- /dev/null +++ b/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js @@ -0,0 +1,36 @@ +const { MwlItemModel } = require("@models/sql/models/mwlItems.model"); +const { GetMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); +const { cloneDeep } = require("lodash"); + +class SqlGetMwlItemService extends GetMwlItemService { + constructor(req, res) { + super(req, res); + } + + async getMwlItems() { + let queryOptions = { + query: this.query, + skip: this.skip_, + limit: this.limit_, + requestParams: this.request.params + }; + + let docs = await MwlItemModel.getDicomJson(queryOptions); + + return docs; + } + + initQuery_() { + let query = cloneDeep(this.request.query); + let queryKeys = Object.keys(query).sort(); + for (let i = 0; i < queryKeys.length; i++) { + let queryKey = queryKeys[i]; + if (!query[queryKey]) delete query[queryKey]; + } + + this.query = convertAllQueryToDicomTag(query, false); + } +} + +module.exports.GetMwlItemService = SqlGetMwlItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js b/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js new file mode 100644 index 00000000..31d04db5 --- /dev/null +++ b/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js @@ -0,0 +1,227 @@ +const sequelize = require("@models/sql/instance"); +const { PatientQueryBuilder } = require("../../../QIDO-RS/service/patientQueryBuilder"); +const { BaseQueryBuilder } = require("../../../QIDO-RS/service/querybuilder"); +const { DicomCodeQueryBuilder } = require("../../../QIDO-RS/service/dicomCodeQueryBuilder"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); + +class MwlQueryBuilder extends BaseQueryBuilder { + constructor(queryOptions) { + super(queryOptions); + + let patientQueryBuilder = new PatientQueryBuilder(queryOptions); + let patientQuery = patientQueryBuilder.build(); + this.includeQueries.push({ + model: sequelize.model("Patient"), + attributes: ["x00100020"], + ...patientQuery, + required: true + }); + + this.createCodeQueries(dictionary.keyword.InstitutionalDepartmentTypeCodeSequence); + this.createCodeQueries(dictionary.keyword.InstitutionCodeSequence); + this.createCodeQueries(dictionary.keyword.ScheduledProtocolCodeSequence); + this.createIssuerOfAccessionNumberSequenceQueries(); + this.createIssuerOfAdmissionIdSequenceQueries(); + this.createSpsQueries(); + } + + getStudyInstanceUID(values) { + return this.getOrQuery( + dictionary.keyword.StudyInstanceUID, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + getAccessionNumber(values) { + return this.getOrQuery( + dictionary.keyword.AccessionNumber, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + getRequestedProcedureID(values) { + return this.getOrQuery( + dictionary.keyword.RequestedProcedureID, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + getAdmissionID(values) { + return this.getOrQuery( + dictionary.keyword.AdmissionID, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + getInstitutionName(values) { + return this.getOrQuery( + dictionary.keyword.InstitutionName, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + getInstitutionDepartmentName(values) { + return this.getOrQuery( + dictionary.keyword.InstitutionalDepartmentName, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + } + + createCodeQueries(tag) { + let dicomCodeQueryBuilder = new DicomCodeQueryBuilder(this, dictionary.tag[tag]); + this[`${tag}.00080100`] = (values) => dicomCodeQueryBuilder.getCodeValue(values); + this[`${tag}.00080102`] = (values) => dicomCodeQueryBuilder.getCodingSchemeDesignator(values); + this[`${tag}.00080103`] = (values) => dicomCodeQueryBuilder.getCodingSchemeVersion(values); + this[`${tag}.00080104`] = (values) => dicomCodeQueryBuilder.getCodeMeaning(values); + } + + createIssuerOfAccessionNumberSequenceQueries() { + this[`${dictionary.keyword.IssuerOfAccessionNumberSequence}.${dictionary.keyword.LocalNamespaceEntityID}`] = (values) => { + return this.getOrQuery( + `accno_local_id`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.IssuerOfAccessionNumberSequence}.${dictionary.keyword.UniversalEntityID}`] = (values) => { + return this.getOrQuery( + `accno_universal_id`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.IssuerOfAccessionNumberSequence}.${dictionary.keyword.UniversalEntityIDType}`] = (values) => { + return this.getOrQuery( + `accno_universal_id_type`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + } + + createIssuerOfAdmissionIdSequenceQueries() { + this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.LocalNamespaceEntityID}`] = (values) => { + return this.getOrQuery( + `issuer_admission_local_id`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityID}`] = (values) => { + return this.getOrQuery( + `issuer_admission_universal_id`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityIDType}`] = (values) => { + return this.getOrQuery( + `issuer_admission_universal_id_type`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + } + + createSpsQueries() { + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.StationAETitle}`] = (values) => { + return this.getOrQuery( + `station_ae_title`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.StationName}`] = (values) => { + return this.getOrQuery( + `station_name`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepStartDate}`] = (values) => { + return this.getOrQuery( + `start_date`, + values, + BaseQueryBuilder.prototype.getDateQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepEndDate}`] = (values) => { + return this.getOrQuery( + `end_date`, + values, + BaseQueryBuilder.prototype.getDateQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepStartTime}`] = (values) => { + return this.getOrQuery( + `start_time`, + values, + BaseQueryBuilder.prototype.getTimeQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepEndTime}`] = (values) => { + return this.getOrQuery( + `end_time`, + values, + BaseQueryBuilder.prototype.getTimeQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledPerformingPhysicianName}`] = (values) => { + let q = this.getOrQuery( + `physician_name`, + values, + BaseQueryBuilder.prototype.getPersonNameQuery.bind(this) + ); + this.includeQueries.push({ + model: sequelize.model("PersonName"), + as: dictionary.tag["00400006"], + where: { + ...q + }, + attributes: [] + }); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepDescription}`] = (values) => { + return this.getOrQuery( + `description`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepID}`] = (values) => { + return this.getOrQuery( + `sps_id`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + + this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepStatus}`] = (values) => { + return this.getOrQuery( + `sps_status`, + values, + BaseQueryBuilder.prototype.getStringQuery.bind(this) + ); + }; + } +} + +module.exports.MwlQueryBuilder = MwlQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 8dd40cbc..87359185 100644 --- a/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -1,7 +1,8 @@ -const _ = require("lodash"); +const { cloneDeep } = require("lodash"); const { GetWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/get-workItem.service"); const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { WorkItemModel } = require("@models/sql/models/workitems.model"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); class SqlGetWorkItemService extends GetWorkItemService { constructor(req, res) { @@ -20,6 +21,17 @@ class SqlGetWorkItemService extends GetWorkItemService { return this.adjustDocs(docs); } + + initQuery_() { + let query = cloneDeep(this.request.query); + let queryKeys = Object.keys(query).sort(); + for (let i = 0; i < queryKeys.length; i++) { + let queryKey = queryKeys[i]; + if (!query[queryKey]) delete query[queryKey]; + } + + this.query = convertAllQueryToDicomTag(query, false); + } } SqlGetWorkItemService.prototype.initQuery_ = QidoRsService.prototype.initQuery_; diff --git a/api/dicom-web/controller/MWL-RS/get-mwlItem.js b/api/dicom-web/controller/MWL-RS/get-mwlItem.js index 1aea7542..b3065fec 100644 --- a/api/dicom-web/controller/MWL-RS/get-mwlItem.js +++ b/api/dicom-web/controller/MWL-RS/get-mwlItem.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { GetMwlItemService } = require("./service/get-mwlItem.service"); +const { GetMwlItemService } = require("@mwl-service/get-mwlItem.service"); class GetMwlItemController extends Controller { constructor(req, res) { diff --git a/models/sql/models/mwlItems.model.js b/models/sql/models/mwlItems.model.js index 165ba1f4..51267b0e 100644 --- a/models/sql/models/mwlItems.model.js +++ b/models/sql/models/mwlItems.model.js @@ -3,6 +3,7 @@ const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { raccoonConfig } = require("@root/config-class"); const { DicomJsonModel } = require("../dicom-json-model"); +const { MwlQueryBuilder } = require("@root/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder"); let Common; if (raccoonConfig.dicomDimseConfig.enableDimse) { @@ -21,7 +22,19 @@ class MwlItemModel extends Model { return new DicomJsonModel(this.json); } static async getDicomJson (queryOptions) { - // TODO: implement + let queryBuilder = new MwlQueryBuilder(queryOptions); + let q = queryBuilder.build(); + + let mwlItems = await MwlItemModel.findAll({ + ...q, + attributes: ["json"], + limit: queryOptions.limit, + offset: queryOptions.skip + }); + + return await Promise.all(mwlItems.map(async item => { + return item.json; + })); } }; From 188b10dd0e488d31493b482e440224ef54761fd1 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 18:53:12 +0800 Subject: [PATCH 250/365] feat: implement count API for MWL item --- .../MWL-RS/service/count-mwlItem.service.js | 27 +++++++++++++++++++ .../controller/MWL-RS/count-mwlItem.js | 2 +- models/sql/models/mwlItems.model.js | 8 ++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js diff --git a/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js new file mode 100644 index 00000000..ddf851f7 --- /dev/null +++ b/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js @@ -0,0 +1,27 @@ +const { MwlItemModel } = require("@models/sql/models/mwlItems.model"); +const { GetMwlItemCountService } = require("@root/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); +const { cloneDeep } = require("lodash"); + +class SqlGetMwlItemCountService extends GetMwlItemCountService{ + constructor(req, res) { + super(req, res); + } + + async getMwlItemCount() { + return await MwlItemModel.getCount(this.query); + } + + initQuery_() { + let query = cloneDeep(this.request.query); + let queryKeys = Object.keys(query).sort(); + for (let i = 0; i < queryKeys.length; i++) { + let queryKey = queryKeys[i]; + if (!query[queryKey]) delete query[queryKey]; + } + + this.query = convertAllQueryToDicomTag(query, false); + } +} + +module.exports.GetMwlItemCountService = SqlGetMwlItemCountService; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/count-mwlItem.js b/api/dicom-web/controller/MWL-RS/count-mwlItem.js index 2f778ecd..f64debfa 100644 --- a/api/dicom-web/controller/MWL-RS/count-mwlItem.js +++ b/api/dicom-web/controller/MWL-RS/count-mwlItem.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { GetMwlItemCountService } = require("./service/count-mwlItem.service"); +const { GetMwlItemCountService } = require("@mwl-service/count-mwlItem.service"); class GetMwlItemCountController extends Controller { constructor(req, res) { diff --git a/models/sql/models/mwlItems.model.js b/models/sql/models/mwlItems.model.js index 51267b0e..ed7b707e 100644 --- a/models/sql/models/mwlItems.model.js +++ b/models/sql/models/mwlItems.model.js @@ -36,6 +36,14 @@ class MwlItemModel extends Model { return item.json; })); } + + static async getCount(query) { + let queryBuilder = new MwlQueryBuilder({query}); + let q = queryBuilder.build(); + return await this.count({ + ...q + }); + } }; /** @type { import("sequelize").ModelAttributes } */ From af8bc58215c1a2b9e2bd8757f9439471460bd9ea Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 18:57:44 +0800 Subject: [PATCH 251/365] refactor: rename to `mwlitems.model.js` --- .../controller/MWL-RS/service/count-mwlItem.service.js | 2 +- .../dicom-web/controller/MWL-RS/service/get-mwlItem.service.js | 2 +- models/sql/init.js | 2 +- models/sql/models/{mwlItems.model.js => mwlitems.model.js} | 0 models/sql/po/mwlItem.po.js | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename models/sql/models/{mwlItems.model.js => mwlitems.model.js} (100%) diff --git a/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js index ddf851f7..144cbd57 100644 --- a/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js +++ b/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js @@ -1,4 +1,4 @@ -const { MwlItemModel } = require("@models/sql/models/mwlItems.model"); +const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); const { GetMwlItemCountService } = require("@root/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); const { cloneDeep } = require("lodash"); diff --git a/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js index de0ecc40..f6bf5fac 100644 --- a/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js +++ b/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js @@ -1,4 +1,4 @@ -const { MwlItemModel } = require("@models/sql/models/mwlItems.model"); +const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); const { GetMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); const { cloneDeep } = require("lodash"); diff --git a/models/sql/init.js b/models/sql/init.js index 7cfc9b18..819276df 100644 --- a/models/sql/init.js +++ b/models/sql/init.js @@ -15,7 +15,7 @@ const { WorkItemModel } = require("./models/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UpsSubscriptionModel } = require("./models/upsSubscription.model"); const { UpsRequestAttributesModel } = require("./models/upsRequestAttributes.model"); -const { MwlItemModel } = require("./models/mwlItems.model"); +const { MwlItemModel } = require("./models/mwlitems.model"); async function initDatabasePostgres() { const { Client } = require("pg"); diff --git a/models/sql/models/mwlItems.model.js b/models/sql/models/mwlitems.model.js similarity index 100% rename from models/sql/models/mwlItems.model.js rename to models/sql/models/mwlitems.model.js diff --git a/models/sql/po/mwlItem.po.js b/models/sql/po/mwlItem.po.js index 24e953f5..c377429a 100644 --- a/models/sql/po/mwlItem.po.js +++ b/models/sql/po/mwlItem.po.js @@ -5,7 +5,7 @@ const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { vrValueTransform } = require("./utils"); const { DicomCodeModel } = require("../models/dicomCode.model"); -const { MwlItemModel } = require("../models/mwlItems.model"); +const { MwlItemModel } = require("../models/mwlitems.model"); const { Op } = require("sequelize"); From e5c20b683e9704db23ded71a840b2326d8d23ac5 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 20:22:18 +0800 Subject: [PATCH 252/365] feat(sql): implement delete MWL item API --- .../MWL-RS/service/delete-mwlItem.service.js | 2 +- models/sql/models/mwlitems.model.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js index 7400c9d7..634300ff 100644 --- a/api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/delete-mwlItem.service.js @@ -1,5 +1,5 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { MwlItemModel } = require("@dbModels/mwlitems.model"); class DeleteMwlItemService { constructor(req, res) { diff --git a/models/sql/models/mwlitems.model.js b/models/sql/models/mwlitems.model.js index ed7b707e..8757971d 100644 --- a/models/sql/models/mwlitems.model.js +++ b/models/sql/models/mwlitems.model.js @@ -44,6 +44,16 @@ class MwlItemModel extends Model { ...q }); } + + static async deleteByStudyInstanceUIDAndSpsID(studyUID, spsID) { + let deletedCount = await MwlItemModel.destroy({ + where: { + study_instance_uid: studyUID, + sps_id: spsID + } + }); + return { deletedCount }; + } }; /** @type { import("sequelize").ModelAttributes } */ From 7ab6685e29e5e00562af2e1db660e80d32994d30 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 22:54:00 +0800 Subject: [PATCH 253/365] fix: sps not assigned correct - missing child property to merge --- .../controller/MWL-RS/service/create-mwlitem.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js index b546df2d..dd43c762 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js @@ -51,7 +51,7 @@ class CreateMwlItemService { } mwlItem[dictionary.keyword.ScheduledProcedureStepSequence] = { ...mwlItem[dictionary.keyword.ScheduledProcedureStepSequence], - ...spsItem.dicomJson + ...spsItem.dicomJson[dictionary.keyword.ScheduledProcedureStepSequence] }; if (!mwlDicomJson.getValue(dictionary.keyword.RequestedProcedureID)) { From 9e91bd3f91f7fa636dcf4d7abad09b5348fab032 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 21 Dec 2023 22:56:24 +0800 Subject: [PATCH 254/365] feat(sql): implement MWL change status API --- .../MWL-RS/service/change-mwlItem-status.js | 44 +++++++++++++++++++ .../MWL-RS/change-mwlItem-status.js | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js diff --git a/api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js b/api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js new file mode 100644 index 00000000..746738ae --- /dev/null +++ b/api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js @@ -0,0 +1,44 @@ +const _ = require("lodash"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { ChangeMwlItemStatusService } = require("@root/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status"); +const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); +const { Op } = require("sequelize"); + +class SqlChangeMwlItemStatusService extends ChangeMwlItemStatusService { + constructor(req, res) { + super(req, res); + } + + async changeMwlItemsStatus() { + let { status } = this.request.params; + let mwlItem = await this.getMwlItemByStudyUIDAndSpsID(); + if (!mwlItem) { + throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "No such object instance", 404); + } + + mwlItem.sps_status = status; + _.set(mwlItem.json, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); + mwlItem.changed("json", true); + await mwlItem.save(); + + return mwlItem.json; + } + + async getMwlItemByStudyUIDAndSpsID() { + return await MwlItemModel.findOne({ + where: { + [Op.and]: [ + { + sps_id: this.request.params.spsID + }, + { + study_instance_uid: this.request.params.studyUID + } + ] + } + }); + } +} + +module.exports.ChangeMwlItemStatusService = SqlChangeMwlItemStatusService; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js index c80492f4..5b7a04aa 100644 --- a/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { ChangeMwlItemStatusService } = require("./service/change-mwlItem-status"); +const { ChangeMwlItemStatusService } = require("@mwl-service/change-mwlItem-status"); class ChangeMwlItemStatusController extends Controller { constructor(req, res) { From 6b223fe13b1cb8de7e56e8372927c769eab1bb25 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 22 Dec 2023 09:33:40 +0800 Subject: [PATCH 255/365] feat(sql): implement change status for filtered MWL item API --- .../service/change-filtered-mwlItem-status.js | 52 +++++++++++++++++++ .../MWL-RS/change-filtered-mwlItem-status.js | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js diff --git a/api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js b/api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js new file mode 100644 index 00000000..b07fb75b --- /dev/null +++ b/api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js @@ -0,0 +1,52 @@ +const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); +const { ChangeFilteredMwlItemStatusService } = require("@root/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status"); +const { cloneDeep, set } = require("lodash"); +const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); +const { MwlQueryBuilder } = require("./query/mwlQueryBuilder"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); + +class SqlChangeFilteredMwlItemStatusService extends ChangeFilteredMwlItemStatusService { + constructor(req, res) { + super(req, res); + } + + async changeMwlItemsStatus() { + let { status } = this.request.params; + let mwlItems = await this.getMwlItems(); + + if (mwlItems.length === 0) { + throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "Can not found any MWL item from query", 404); + } + + for (let mwlItem of mwlItems) { + mwlItem.status = status; + set(mwlItem.json, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); + mwlItem.changed("json", true); + await mwlItem.save(); + } + + return mwlItems.length; + } + + async getMwlItems() { + let queryBuilder = new MwlQueryBuilder({ + query: this.query + }); + let q = queryBuilder.build(); + return await MwlItemModel.findAll(q); + } + + initQuery_() { + let query = cloneDeep(this.request.query); + let queryKeys = Object.keys(query).sort(); + for (let i = 0; i < queryKeys.length; i++) { + let queryKey = queryKeys[i]; + if (!query[queryKey]) delete query[queryKey]; + } + + this.query = convertAllQueryToDicomTag(query, false); + } +} + +module.exports.ChangeFilteredMwlItemStatusService = SqlChangeFilteredMwlItemStatusService; \ No newline at end of file diff --git a/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js index 3c9ef4af..9836a99a 100644 --- a/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { ChangeFilteredMwlItemStatusService } = require("./service/change-filtered-mwlItem-status"); +const { ChangeFilteredMwlItemStatusService } = require("@mwl-service/change-filtered-mwlItem-status"); class ChangeFilteredMwlItemStatusController extends Controller { constructor(req, res) { From ae4627c868f4f775ceac43de29cab66d8adf79cf Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 22 Dec 2023 09:45:45 +0800 Subject: [PATCH 256/365] fix(sql): missing prefix x for nested tag of query field --- api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js index 6e21e833..f4222271 100644 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js @@ -380,7 +380,7 @@ class BaseQueryBuilder { * @returns */ getQueryField(tag) { - return /^[0-9a-zA-Z]{8}$/.test(tag) ? `x${tag}` : tag; + return /^[0-9a-zA-Z]{8}$/.test(tag.substring(0, 8)) ? `x${tag}` : tag; } } From f08bf44373c7dba1862cd709da1fa9d465131cb5 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 22 Dec 2023 10:03:05 +0800 Subject: [PATCH 257/365] fix(mongodb): toDicomJsonModel is not a function that missing consistent --- models/mongodb/models/workitems.model.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index 67766951..35a5ebb8 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -4,6 +4,7 @@ const _ = require("lodash"); const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { SUBSCRIPTION_STATE } = require("../../DICOM/ups"); +const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); let workItemSchema = new mongoose.Schema( { @@ -34,6 +35,11 @@ let workItemSchema = new mongoose.Schema( versionKey: false, toObject: { getters: true + }, + methods: { + toDicomJsonModel: function () { + return new DicomJsonModel(this); + } } } ); From cb2d263163a79e73f24158d0f6e52d18f96ede38 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 22 Dec 2023 10:12:21 +0800 Subject: [PATCH 258/365] test: load mongodb module-alias default --- test/before.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/before.js b/test/before.js index e272a9ab..3a9bb891 100644 --- a/test/before.js +++ b/test/before.js @@ -1,4 +1,4 @@ -require("module-alias/register"); +require('module-alias')(__dirname + "/../config/modula-alias/mongodb"); const mongoose = require("mongoose"); const { MongoMemoryServer } = require("mongodb-memory-server"); const {getLogger} = require("log4js"); From 2b4e5db5c97edb682e00e9a254d65fdcf2bbb1d4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 22 Dec 2023 10:39:12 +0800 Subject: [PATCH 259/365] fix: not exist function `writeSpecificFramesRenderedImages` - Use `writeRenderedImages` instead --- .../dicom-web/controller/WADO-RS/service/rendered.service.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js index cdaab2ed..2a303f7f 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -2,7 +2,6 @@ const { handleImageQuality, handleViewport, writeRenderedImages, - writeSpecificFramesRenderedImages, RenderedImageMultipartWriter } = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); const fs = require("fs"); @@ -150,7 +149,7 @@ class InstanceFramesListWriter extends FramesWriter { return this.writeSingleFrame(); } else { let multipartWriter = new MultipartWriter([], this.request, this.response); - await writeSpecificFramesRenderedImages(this.request, frameNumber, this.instanceFramesObj, multipartWriter); + await writeRenderedImages(this.request, frameNumber, this.instanceFramesObj, multipartWriter); multipartWriter.writeFinalBoundary(); return true; } @@ -309,7 +308,6 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { module.exports.postProcessFrameImage = postProcessFrameImage; module.exports.writeRenderedImages = writeRenderedImages; -module.exports.writeSpecificFramesRenderedImages = writeSpecificFramesRenderedImages; module.exports.getInstanceFrameObj = getInstanceFrameObj; module.exports.RenderedImageMultipartWriter = RenderedImageMultipartWriter; module.exports.StudyFramesWriter = StudyFramesWriter; From 1f6375792e0a4e1369fcae6bd175ff21d3efda43 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 22 Dec 2023 16:30:50 +0800 Subject: [PATCH 260/365] feat(mongodb): implement create patient of PAM-RS - Default media info for patient schema - Add toDicomJson to get DICOM Json Item --- .../controller/PAM-RS/create-patient.js | 33 +++++++++++++++++++ .../PAM-RS/service/create-patient.service.js | 23 +++++++++++++ api/dicom-web/pam-rs.route.js | 31 +++++++++++++++++ models/mongodb/models/patient.model.js | 32 ++++++++++++++++++ routes.js | 1 + 5 files changed, 120 insertions(+) create mode 100644 api/dicom-web/controller/PAM-RS/create-patient.js create mode 100644 api/dicom-web/controller/PAM-RS/service/create-patient.service.js create mode 100644 api/dicom-web/pam-rs.route.js diff --git a/api/dicom-web/controller/PAM-RS/create-patient.js b/api/dicom-web/controller/PAM-RS/create-patient.js new file mode 100644 index 00000000..9030ad6a --- /dev/null +++ b/api/dicom-web/controller/PAM-RS/create-patient.js @@ -0,0 +1,33 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { CreatePatientService } = require("./service/create-patient.service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); + +class CreatePatientController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "PAM-RS"); + this.apiLogger.addTokenValue(); + } + async mainProcess() { + this.apiLogger.logger.info("Create Patient"); + + let createPatientService = new CreatePatientService(this.request, this.response); + try { + let createPatient = await createPatientService.create(); + return this.response + .set("content-type", "application/dicom+json") + .status(201) + .json(createPatient.toDicomJson()); + } catch(e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +module.exports = async function (req, res) { + let controller = new CreatePatientController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/PAM-RS/service/create-patient.service.js b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js new file mode 100644 index 00000000..ad2a2a48 --- /dev/null +++ b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js @@ -0,0 +1,23 @@ +const { PatientModel } = require("@models/mongodb/models/patient.model"); +const { set, get } = require("lodash"); + +class CreatePatientService { + /** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + */ + constructor(req, res) { + this.request = req; + this.response = res; + } + + async create() { + let incomingPatient = this.request.body; + set(incomingPatient, "patientID", get(incomingPatient, "00100020.Value.0")); + const patient = new PatientModel(incomingPatient); + return await patient.save(); + } +} + +module.exports.CreatePatientService = CreatePatientService; \ No newline at end of file diff --git a/api/dicom-web/pam-rs.route.js b/api/dicom-web/pam-rs.route.js new file mode 100644 index 00000000..a1f5665d --- /dev/null +++ b/api/dicom-web/pam-rs.route.js @@ -0,0 +1,31 @@ +const express = require("express"); +const router = express(); +const Joi = require("joi"); +const { validateParams } = require("../validator"); + + +/** + * @openapi + * /dicom-web/patients: + * post: + * tags: + * - PAM-RS + * description: Create new patient + * responses: + * 200: + * description: Create patient successfully + * content: + * "application/dicom+json": + * schema: + * $ref: "#/components/schemas/PatientRequiredMatchingAttributes" + * + */ +router.post("/patients", validateParams({ + "00100020": Joi.object({ + "vr": Joi.string().required().allow("LO"), + "Value": Joi.array().items(Joi.string()).required().min(1) + }).required() +}, "body", { allowUnknown: true }), require("./controller/PAM-RS/create-patient")); + + +module.exports = router; \ No newline at end of file diff --git a/models/mongodb/models/patient.model.js b/models/mongodb/models/patient.model.js index 78ce4b8d..a8005304 100644 --- a/models/mongodb/models/patient.model.js +++ b/models/mongodb/models/patient.model.js @@ -7,10 +7,25 @@ const { } = require("../../DICOM/dicom-tags-mapping"); const { raccoonConfig } = require("@root/config-class"); const { DicomSchemaOptionsFactory, PatientDocDicomJsonHandler } = require("../schema/dicom.schema"); +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); let patientSchemaOptions = _.merge( DicomSchemaOptionsFactory.get("patient", PatientDocDicomJsonHandler), { + methods: { + toDicomJson: function() { + let obj = this.toObject(); + delete obj._id; + delete obj.id; + delete obj.patientID; + delete obj.studyPaths; + delete obj.deleteStatus; + delete obj.createdAt; + delete obj.updatedAt; + + return obj; + } + }, statics: { getPathGroupQuery: function (iParam) { let { patientID } = iParam; @@ -83,6 +98,23 @@ for (let tag in tagsNeedStore.Patient) { }); } +// default storage media file set info from config +patientSchema[dictionary.keyword.StorageMediaFileSetID] = { + ...patientSchema[dictionary.keyword.StorageMediaFileSetID], + default: { + vr: "SH", + Value: raccoonConfig.mediaStorageID + } +}; + +patientSchema[dictionary.keyword.StorageMediaFileSetUID] = { + ...patientSchema[dictionary.keyword.StorageMediaFileSetUID], + default: { + vr: "UI", + Value: raccoonConfig.mediaStorageUID + } +}; + // Index patient id patientSchema.index({ patientID: 1 diff --git a/routes.js b/routes.js index 60587fb4..e6997140 100644 --- a/routes.js +++ b/routes.js @@ -29,6 +29,7 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/delete.route")); app.use("/dicom-web", require("./api/dicom-web/ups-rs.route")); app.use("/dicom-web", require("./api/dicom-web/mwl-rs.route")); + app.use("/dicom-web", require("./api/dicom-web/pam-rs.route")); app.use("/wado", require("./api/WADO-URI")); }; From 371a5ec965be751aa93cc9afa3595f78f695bdaf Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 14:33:45 +0800 Subject: [PATCH 261/365] docs: update openapi - Fixed type error in - `mwl-rs.route.js` - `ups-rs.route.js` - Fixed missing name in `filter` parameter - Fixed wrong position of enum of `spsStatus` for mwl - Fixed wrong in of `binaryValuePath`, and missing required - Fixed missing `patientID` for patient --- api/dicom-web/mwl-rs.route.js | 15 +- api/dicom-web/ups-rs.route.js | 4 + docs/swagger/openapi.json | 333 ++++++++++++++++--- docs/swagger/parameters/dicomweb-common.yaml | 17 +- docs/swagger/parameters/mwl.yaml | 9 +- docs/swagger/parameters/patient.yaml | 10 +- docs/swagger/parameters/wado-rs.yaml | 3 +- 7 files changed, 320 insertions(+), 71 deletions(-) diff --git a/api/dicom-web/mwl-rs.route.js b/api/dicom-web/mwl-rs.route.js index 06e0e0dd..a955f071 100644 --- a/api/dicom-web/mwl-rs.route.js +++ b/api/dicom-web/mwl-rs.route.js @@ -12,10 +12,6 @@ const router = express(); * - MWL-RS * description: > * This transaction create or update a Modality WorkList item. - * requestBody: - * content: - * application/dicom+json: - * parameters: * responses: * "200": * description: The workitem create successfully @@ -53,6 +49,8 @@ router.post("/mwlitems", * "application/dicom+json": * schema: * type: array + * items: + * type: object */ router.get("/mwlitems",require("./controller/MWL-RS/get-mwlItem")); @@ -86,9 +84,6 @@ router.get("/mwlitems/count",require("./controller/MWL-RS/count-mwlItem")); * - MWL-RS * description: > * This transaction deletes a Modality WorkList item. - * requestBody: - * content: - * application/dicom+json: * parameters: * - $ref: "#/components/parameters/studyUID" * - $ref: "#/components/parameters/spsID" @@ -110,9 +105,6 @@ router.delete("/mwlitems/:studyUID/:spsID", validateParams({ * - MWL-RS * description: > * This transaction create or update a Modality WorkList item. - * requestBody: - * content: - * application/dicom+json: * parameters: * - $ref: "#/components/parameters/studyUID" * - $ref: "#/components/parameters/spsID" @@ -139,9 +131,6 @@ router.post("/mwlitems/:studyUID/:spsID/status/:status", * - MWL-RS * description: > * This transaction create or update a Modality WorkList item. - * requestBody: - * content: - * application/dicom+json: * parameters: * - $ref: "#/components/parameters/spsStatus" * - $ref: "#/components/parameters/filter" diff --git a/api/dicom-web/ups-rs.route.js b/api/dicom-web/ups-rs.route.js index 7e08fa56..a04a0b40 100644 --- a/api/dicom-web/ups-rs.route.js +++ b/api/dicom-web/ups-rs.route.js @@ -53,6 +53,8 @@ router.post("/workitems", * "application/dicom+json": * schema: * type: array + * items: + * type: object */ router.get("/workitems", require("./controller/UPS-RS/get-workItem") @@ -76,6 +78,8 @@ router.get("/workitems", * "application/dicom+json": * schema: * type: array + * items: + * type: object */ router.get("/workitems/:workItem", require("./controller/UPS-RS/get-workItem") diff --git a/docs/swagger/openapi.json b/docs/swagger/openapi.json index 65a0bc3f..607403b8 100644 --- a/docs/swagger/openapi.json +++ b/docs/swagger/openapi.json @@ -6,6 +6,239 @@ }, "openapi": "3.0.0", "paths": { + "/dicom-web/mwlitems": { + "post": { + "tags": [ + "MWL-RS" + ], + "description": "This transaction create or update a Modality WorkList item.\n", + "responses": { + "200": { + "description": "The workitem create successfully" + } + } + }, + "get": { + "tags": [ + "MWL-RS" + ], + "description": "This transaction search Modality WorkList items.\n", + "parameters": [ + { + "$ref": "#/components/parameters/filter" + } + ], + "responses": { + "200": { + "description": "Query successfully", + "content": { + "application/dicom+json": { + "schema": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + }, + "/dicom-web/mwlitems/count": { + "get": { + "tags": [ + "MWL-RS" + ], + "description": "This transaction get Modality WorkList items count.\n", + "parameters": [ + { + "$ref": "#/components/parameters/filter" + } + ], + "responses": { + "200": { + "description": "Query successfully", + "content": { + "application/dicom+json": { + "schema": { + "properties": { + "count": { + "type": "number" + } + } + } + } + } + } + } + } + }, + "/dicom-web/mwlitems/{studyUID}/{spsID}": { + "delete": { + "tags": [ + "MWL-RS" + ], + "description": "This transaction deletes a Modality WorkList item.\n", + "parameters": [ + { + "$ref": "#/components/parameters/studyUID" + }, + { + "$ref": "#/components/parameters/spsID" + } + ], + "responses": { + "204": { + "description": "Delete successfully" + } + } + } + }, + "/dicom-web/mwlitems/{studyUID}/{spsID}/status/{spsStatus}": { + "post": { + "tags": [ + "MWL-RS" + ], + "description": "This transaction create or update a Modality WorkList item.\n", + "parameters": [ + { + "$ref": "#/components/parameters/studyUID" + }, + { + "$ref": "#/components/parameters/spsID" + }, + { + "$ref": "#/components/parameters/spsStatus" + } + ], + "responses": { + "200": { + "description": "change status of mwl item successfully" + } + } + } + }, + "/dicom-web/mwlitems/status/{spsStatus}": { + "post": { + "tags": [ + "MWL-RS" + ], + "description": "This transaction create or update a Modality WorkList item.\n", + "parameters": [ + { + "$ref": "#/components/parameters/spsStatus" + }, + { + "$ref": "#/components/parameters/filter" + } + ], + "responses": { + "200": { + "description": "change status of mwl items successfully" + } + } + } + }, + "/dicom-web/patients": { + "post": { + "tags": [ + "PAM-RS" + ], + "description": "Create new patient", + "responses": { + "200": { + "description": "Create patient successfully", + "content": { + "application/dicom+json": { + "schema": { + "$ref": "#/components/schemas/PatientRequiredMatchingAttributes" + } + } + } + } + } + }, + "get": { + "tags": [ + "QIDO-RS" + ], + "description": "Query all patients in server", + "parameters": [ + { + "$ref": "#/components/parameters/PatientName" + }, + { + "$ref": "#/components/parameters/PatientID" + }, + { + "$ref": "#/components/parameters/PatientBirthDate" + }, + { + "$ref": "#/components/parameters/PatientBirthTime" + } + ], + "responses": { + "200": { + "description": "Query successfully", + "content": { + "application/dicom+json": { + "schema": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/PatientRequiredMatchingAttributes" + } + ] + } + } + } + } + } + } + } + }, + "/dicom-web/patients/{patientID}": { + "put": { + "tags": [ + "PAM-RS" + ], + "description": "Create new patient", + "parameters": [ + { + "$ref": "#/components/parameters/patientID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/dicom+json": { + "schema": { + "$ref": "#/components/schemas/PatientRequiredMatchingAttributes" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatientRequiredMatchingAttributes" + } + } + } + }, + "responses": { + "200": { + "description": "Create patient successfully", + "content": { + "application/dicom+json": { + "schema": { + "$ref": "#/components/schemas/PatientRequiredMatchingAttributes" + } + } + } + } + } + } + }, "/dicom-web/studies": { "get": { "tags": [ @@ -439,47 +672,6 @@ } } }, - "/dicom-web/patients": { - "get": { - "tags": [ - "QIDO-RS" - ], - "description": "Query all patients in server", - "parameters": [ - { - "$ref": "#/components/parameters/PatientName" - }, - { - "$ref": "#/components/parameters/PatientID" - }, - { - "$ref": "#/components/parameters/PatientBirthDate" - }, - { - "$ref": "#/components/parameters/PatientBirthTime" - } - ], - "responses": { - "200": { - "description": "Query successfully", - "content": { - "application/dicom+json": { - "schema": { - "type": "array", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/PatientRequiredMatchingAttributes" - } - ] - } - } - } - } - } - } - } - }, "/dicom-web/workitems": { "post": { "tags": [ @@ -508,7 +700,10 @@ "content": { "application/dicom+json": { "schema": { - "type": "array" + "type": "array", + "items": { + "type": "object" + } } } } @@ -533,7 +728,10 @@ "content": { "application/dicom+json": { "schema": { - "type": "array" + "type": "array", + "items": { + "type": "object" + } } } } @@ -1243,6 +1441,43 @@ } }, "parameters": { + "filter": { + "name": "filter", + "description": "{attributeID}={value}; {attributeID} = {dicomTag} | {dicomKeyword} | {dicomTag}.{attributeID} | {dicomKeyword}.{attributeID}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "spsID": { + "in": "path", + "name": "spsID", + "required": true, + "schema": { + "type": "string" + } + }, + "spsStatus": { + "in": "path", + "name": "spsStatus", + "required": true, + "schema": { + "type": "string", + "enum": [ + "SCHEDULED", + "ARRIVED", + "READY", + "STARTED", + "DEPARTED", + "CANCELED", + "DISCONTINUED", + "COMPLETED" + ] + } + }, "PatientName": { "name": "00100010", "description": "Patient's full name.", @@ -1275,6 +1510,15 @@ "type": "string" } }, + "patientID": { + "name": "patientID", + "description": "Patient ID", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, "studyUID": { "name": "studyUID", "in": "path", @@ -1431,7 +1675,8 @@ "binaryValuePath": { "name": "binaryValuePath", "description": "{dicomTag} or {dicomTag.InlineBinary} or {dicomTag.Value.itemIndex.Tag}", - "in": "query", + "in": "path", + "required": true, "schema": { "type": "string" } diff --git a/docs/swagger/parameters/dicomweb-common.yaml b/docs/swagger/parameters/dicomweb-common.yaml index 2175fd21..fb151cfa 100644 --- a/docs/swagger/parameters/dicomweb-common.yaml +++ b/docs/swagger/parameters/dicomweb-common.yaml @@ -29,11 +29,12 @@ components: schema: type: string format: byte - parameters: - "filter": - description: "{attributeID}={value}; {attributeID} = {dicomTag} | {dicomKeyword} | {dicomTag}.{attributeID} | {dicomKeyword}.{attributeID}" - in: query - schema: - type: array - items: - type: string \ No newline at end of file + parameters: + "filter": + name: filter + description: "{attributeID}={value}; {attributeID} = {dicomTag} | {dicomKeyword} | {dicomTag}.{attributeID} | {dicomKeyword}.{attributeID}" + in: query + schema: + type: array + items: + type: string \ No newline at end of file diff --git a/docs/swagger/parameters/mwl.yaml b/docs/swagger/parameters/mwl.yaml index f5459729..af65c85f 100644 --- a/docs/swagger/parameters/mwl.yaml +++ b/docs/swagger/parameters/mwl.yaml @@ -10,7 +10,10 @@ components: in: path name: spsStatus required: true - enum: + + schema: + type: string + enum: - SCHEDULED - ARRIVED - READY @@ -18,6 +21,4 @@ components: - DEPARTED - CANCELED - DISCONTINUED - - COMPLETED - schema: - type: string \ No newline at end of file + - COMPLETED \ No newline at end of file diff --git a/docs/swagger/parameters/patient.yaml b/docs/swagger/parameters/patient.yaml index 5edbfcbe..5eb5f871 100644 --- a/docs/swagger/parameters/patient.yaml +++ b/docs/swagger/parameters/patient.yaml @@ -25,4 +25,12 @@ components: description: Birth time of the Patient. in: query schema: - type: string \ No newline at end of file + type: string + "patientID": + name: "patientID" + description: Patient ID + in: path + required: true + schema: + type: string + \ No newline at end of file diff --git a/docs/swagger/parameters/wado-rs.yaml b/docs/swagger/parameters/wado-rs.yaml index 290726f0..81fb985e 100644 --- a/docs/swagger/parameters/wado-rs.yaml +++ b/docs/swagger/parameters/wado-rs.yaml @@ -28,6 +28,7 @@ components: "binaryValuePath": name: "binaryValuePath" description: "{dicomTag} or {dicomTag.InlineBinary} or {dicomTag.Value.itemIndex.Tag}" - in: query + in: path + required: true schema: type: string From c5c1a083a4dbe36867cce991557a850a79750057 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 15:18:27 +0800 Subject: [PATCH 262/365] feat(mongodb): implement API `update, create patient by patient ID` for PAM-RS --- .../PAM-RS/service/update-patient.service.js | 36 +++++++++++++++++++ .../controller/PAM-RS/update-patient.js | 34 ++++++++++++++++++ api/dicom-web/pam-rs.route.js | 29 +++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 api/dicom-web/controller/PAM-RS/service/update-patient.service.js create mode 100644 api/dicom-web/controller/PAM-RS/update-patient.js diff --git a/api/dicom-web/controller/PAM-RS/service/update-patient.service.js b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js new file mode 100644 index 00000000..2089893e --- /dev/null +++ b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js @@ -0,0 +1,36 @@ +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { PatientModel } = require("@models/mongodb/models/patient.model"); +const { set } = require("lodash"); + + +class UpdatePatientService { + constructor(req, res) { + /** @type { import("express").Request } */ + this.request = req; + /** @type { import("express").Response } */ + this.response = res; + this.incomingPatient = this.request.body; + this.#adjustIncomingPatient(); + } + + async update() { + let { patientID } = this.request.params; + return await PatientModel.findOneAndUpdate({ + patientID + }, { + $set: { + ...this.incomingPatient + } + }, { + upsert: true, + new: true + }); + } + + #adjustIncomingPatient() { + delete this.incomingPatient[dictionary.keyword.PatientID]; + set(this.incomingPatient, "patientID", this.request.params.patientID); + } +} + +module.exports.UpdatePatientService = UpdatePatientService; \ No newline at end of file diff --git a/api/dicom-web/controller/PAM-RS/update-patient.js b/api/dicom-web/controller/PAM-RS/update-patient.js new file mode 100644 index 00000000..fffb657b --- /dev/null +++ b/api/dicom-web/controller/PAM-RS/update-patient.js @@ -0,0 +1,34 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { CreatePatientService } = require("./service/create-patient.service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { UpdatePatientService } = require("./service/update-patient.service"); + +class UpdatePatientController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "PAM-RS"); + this.apiLogger.addTokenValue(); + } + async mainProcess() { + this.apiLogger.logger.info(`Update Patient: ${this.request.params.patientID}`); + + let updatePatientService = new UpdatePatientService(this.request, this.response); + try { + let updatedPatient = await updatePatientService.update(); + return this.response + .set("content-type", "application/dicom+json") + .status(200) + .json(updatedPatient.toDicomJson()); + } catch(e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +module.exports = async function (req, res) { + let controller = new UpdatePatientController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/pam-rs.route.js b/api/dicom-web/pam-rs.route.js index a1f5665d..8355018f 100644 --- a/api/dicom-web/pam-rs.route.js +++ b/api/dicom-web/pam-rs.route.js @@ -28,4 +28,33 @@ router.post("/patients", validateParams({ }, "body", { allowUnknown: true }), require("./controller/PAM-RS/create-patient")); +/** + * @openapi + * /dicom-web/patients/{patientID}: + * put: + * tags: + * - PAM-RS + * description: Create new patient + * parameters: + * - $ref: "#/components/parameters/patientID" + * requestBody: + * required: true + * content: + * "application/dicom+json": + * schema: + * $ref: "#/components/schemas/PatientRequiredMatchingAttributes" + * "application/json": + * schema: + * $ref: "#/components/schemas/PatientRequiredMatchingAttributes" + * responses: + * 200: + * description: Create patient successfully + * content: + * "application/dicom+json": + * schema: + * $ref: "#/components/schemas/PatientRequiredMatchingAttributes" + * + */ +router.put("/patients/:patientID", require("./controller/PAM-RS/update-patient")); + module.exports = router; \ No newline at end of file From 565fc7f43b1cdd10720d057585da2ff2351fa36c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 16:03:24 +0800 Subject: [PATCH 263/365] feat(mongodb): generate PatientID from server for create patient API --- api/dicom-web/controller/PAM-RS/create-patient.js | 4 ++-- .../PAM-RS/service/create-patient.service.js | 13 +++++++++++-- api/dicom-web/pam-rs.route.js | 5 ++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/api/dicom-web/controller/PAM-RS/create-patient.js b/api/dicom-web/controller/PAM-RS/create-patient.js index 9030ad6a..b7a2a421 100644 --- a/api/dicom-web/controller/PAM-RS/create-patient.js +++ b/api/dicom-web/controller/PAM-RS/create-patient.js @@ -14,11 +14,11 @@ class CreatePatientController extends Controller { let createPatientService = new CreatePatientService(this.request, this.response); try { - let createPatient = await createPatientService.create(); + let createPatientID = await createPatientService.create(); return this.response .set("content-type", "application/dicom+json") .status(201) - .json(createPatient.toDicomJson()); + .json(createPatientID); } catch(e) { let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); return apiErrorArrayHandler.doErrorResponse(); diff --git a/api/dicom-web/controller/PAM-RS/service/create-patient.service.js b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js index ad2a2a48..7043ca93 100644 --- a/api/dicom-web/controller/PAM-RS/service/create-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js @@ -1,5 +1,7 @@ const { PatientModel } = require("@models/mongodb/models/patient.model"); const { set, get } = require("lodash"); +const shortHash = require("shorthash2"); +const { v4: uuidV4 } = require("uuid"); class CreatePatientService { /** @@ -14,9 +16,16 @@ class CreatePatientService { async create() { let incomingPatient = this.request.body; - set(incomingPatient, "patientID", get(incomingPatient, "00100020.Value.0")); + let patientID = shortHash(uuidV4()); + set(incomingPatient, "patientID", patientID); + set(incomingPatient, "00100020.Value", [ + patientID + ]); const patient = new PatientModel(incomingPatient); - return await patient.save(); + await patient.save(); + return { + patientID + }; } } diff --git a/api/dicom-web/pam-rs.route.js b/api/dicom-web/pam-rs.route.js index 8355018f..50da1144 100644 --- a/api/dicom-web/pam-rs.route.js +++ b/api/dicom-web/pam-rs.route.js @@ -17,7 +17,10 @@ const { validateParams } = require("../validator"); * content: * "application/dicom+json": * schema: - * $ref: "#/components/schemas/PatientRequiredMatchingAttributes" + * type: object + * properties: + * patientID: + * type: string * */ router.post("/patients", validateParams({ From 0f8c39d93b52894ae7672713bd2442816234bf99 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 16:09:41 +0800 Subject: [PATCH 264/365] feat(mongodb): implement delete patient API --- .../controller/PAM-RS/delete-patient.js | 33 +++++++++++++++++++ .../PAM-RS/service/delete-patient.service.js | 27 +++++++++++++++ api/dicom-web/pam-rs.route.js | 2 ++ 3 files changed, 62 insertions(+) create mode 100644 api/dicom-web/controller/PAM-RS/delete-patient.js create mode 100644 api/dicom-web/controller/PAM-RS/service/delete-patient.service.js diff --git a/api/dicom-web/controller/PAM-RS/delete-patient.js b/api/dicom-web/controller/PAM-RS/delete-patient.js new file mode 100644 index 00000000..b7e6f6af --- /dev/null +++ b/api/dicom-web/controller/PAM-RS/delete-patient.js @@ -0,0 +1,33 @@ +const { Controller } = require("@root/api/controller.class"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { DeletePatientService } = require("./service/delete-patient.service"); + +class DeletePatientController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(this.request, "PAM-RS"); + this.apiLogger.addTokenValue(); + } + async mainProcess() { + this.apiLogger.logger.info(`Delete Patient: ${this.request.params.patientID}`); + + let deletePatientService = new DeletePatientService(this.request, this.response); + try { + await deletePatientService.delete(); + return this.response + .set("content-type", "application/dicom+json") + .status(200) + .send(); + } catch(e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +module.exports = async function (req, res) { + let controller = new DeletePatientController(req, res); + + await controller.doPipeline(); +}; \ No newline at end of file diff --git a/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js b/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js new file mode 100644 index 00000000..58aa2c01 --- /dev/null +++ b/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js @@ -0,0 +1,27 @@ +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { PatientModel } = require("@models/mongodb/models/patient.model"); + +class DeletePatientService { + constructor(req, res) { + /** @type { import("express").Request } */ + this.request = req; + /** @type { import("express").Response } */ + this.response = res; + } + + async delete() { + let { patientID } = this.request.params; + let patient = await PatientModel.findOne({ patientID}); + if (!patient) { + throw new DicomWebServiceError( + DicomWebStatusCodes.NoSuchObjectInstance, + `Patient "${this.request.params.patientID}" does not exist.`, + 404 + ); + } + + await patient.incrementDeleteStatus(); + } +} + +module.exports.DeletePatientService = DeletePatientService; \ No newline at end of file diff --git a/api/dicom-web/pam-rs.route.js b/api/dicom-web/pam-rs.route.js index 50da1144..0c64c85c 100644 --- a/api/dicom-web/pam-rs.route.js +++ b/api/dicom-web/pam-rs.route.js @@ -60,4 +60,6 @@ router.post("/patients", validateParams({ */ router.put("/patients/:patientID", require("./controller/PAM-RS/update-patient")); +router.delete("/patients/:patientID", require("./controller/PAM-RS/delete-patient")); + module.exports = router; \ No newline at end of file From 46563d369db4528a78939dbebb0968d39212f1ce Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 17:15:41 +0800 Subject: [PATCH 265/365] feat: delete expired (deleteStatus) patients and associated records --- models/mongodb/deleteSchedule.js | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/models/mongodb/deleteSchedule.js b/models/mongodb/deleteSchedule.js index 271301b7..74003a54 100644 --- a/models/mongodb/deleteSchedule.js +++ b/models/mongodb/deleteSchedule.js @@ -4,9 +4,13 @@ const { logger } = require("@root/utils/logs/log"); const { StudyModel } = require("@dbModels/study.model"); const { InstanceModel } = require("@dbModels/instance.model"); const { SeriesModel } = require("@dbModels/series.model"); +const { PatientModel } = require("./models/patient.model"); // Delete dicom with delete status >= 2 schedule.scheduleJob("*/5 * * * * *", async function () { + deleteExpirePatients().catch(e => { + logger.error(e); + }); deleteExpireStudies().catch((e) => { logger.error(e); }); @@ -18,6 +22,35 @@ schedule.scheduleJob("*/5 * * * * *", async function () { }); }); +async function deleteExpirePatients() { + let deletedPatients = await PatientModel.find({ + deleteStatus: { + $gte: 2 + } + }); + + for (let deletedPatient of deletedPatients) { + let updateAtDate = moment(deletedPatient.updatedAt); + let now = moment(); + let diff = now.diff(updateAtDate, "seconds"); + if (diff >= 30) { + let patientID = deletedPatient.patientID; + logger.info("delete expired patient: " + patientID); + await Promise.all([ + StudyModel.deleteMany({ + "00100020.Value.0": patientID + }), + InstanceModel.deleteMany({ + "00100020.Value.0": patientID + }), + SeriesModel.deleteMany({ + "00100020.Value.0": patientID + }), + deletedPatient.delete() + ]); + } + } +} async function deleteExpireStudies() { let deletedStudies = await StudyModel.find({ From 0e96bdeff2ddf47892a03519904525cd2e5a666e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 18:19:33 +0800 Subject: [PATCH 266/365] fix(mongodb): missing `00100020` in updated patient --- .../controller/PAM-RS/service/update-patient.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dicom-web/controller/PAM-RS/service/update-patient.service.js b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js index 2089893e..d4e082a6 100644 --- a/api/dicom-web/controller/PAM-RS/service/update-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js @@ -28,7 +28,7 @@ class UpdatePatientService { } #adjustIncomingPatient() { - delete this.incomingPatient[dictionary.keyword.PatientID]; + set(this.incomingPatient, "00100020.Value", [this.request.params.patientID]); set(this.incomingPatient, "patientID", this.request.params.patientID); } } From 47769a3d2481fb1be4e7a51a8caf3a7f302cc651 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 18:47:28 +0800 Subject: [PATCH 267/365] feat(mongo): use countDocuments instead of count --- .../controller/MWL-RS/service/create-mwlitem.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js index dd43c762..920b355d 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js @@ -89,7 +89,7 @@ class CreateMwlItemService { async checkPatientExist() { let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); - let patientCount = await PatientModel.count({ + let patientCount = await PatientModel.countDocuments({ patientID }); if (patientCount <= 0) { From 5c4b64e98d8cd5ef2cf4ccedf127e23b9bc75431 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 18:50:11 +0800 Subject: [PATCH 268/365] chore: update particular packages - axios - connect-mongo - mongoose - uuid --- package-lock.json | 1528 +++++++++++++++++++++++++++++++++++---------- package.json | 8 +- 2 files changed, 1199 insertions(+), 337 deletions(-) diff --git a/package-lock.json b/package-lock.json index f41e40f6..38c6a898 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,13 @@ "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", "archiver": "^5.3.1", - "axios": "^0.26.1", + "axios": "^1.6.2", "bcrypt": "^5.0.1", "body-parser": "^1.20.0", "colorette": "^2.0.20", "commander": "^10.0.1", "compression": "^1.7.4", - "connect-mongo": "^4.6.0", + "connect-mongo": "^5.1.0", "connect-session-sequelize": "^7.1.7", "cookie-parser": "^1.4.6", "cors": "^2.8.5", @@ -52,9 +52,9 @@ "request-multipart": "^1.0.0", "run-script-os": "^1.1.6", "sequelize": "^6.32.1", - "sharp": "^0.30.4", + "sharp": "^0.33.1", "shorthash2": "^1.0.3", - "uuid": "^9.0.0", + "uuid": "^9.0.1", "ws": "^8.13.0" }, "devDependencies": { @@ -758,6 +758,15 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/runtime": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", + "integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", @@ -836,6 +845,437 @@ "node": ">=6.9.0" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", + "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", + "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz", + "integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz", + "integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz", + "integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz", + "integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz", + "integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz", + "integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz", + "integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz", + "integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", + "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", + "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", + "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", + "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", + "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", + "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", + "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^0.44.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", + "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", + "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -955,6 +1395,14 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", + "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1547,16 +1995,16 @@ "integrity": "sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==" }, "node_modules/@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.3.tgz", + "integrity": "sha512-z1ELvMijRL1QmU7QuzDkeYXSF2+dXI0ITKoQsIoVKcNBOiK5RMmWy+pYYxJTHFt8vkpZe7UsvRErQwcxZkjoUw==", + "peer": true, "dependencies": { - "@types/node": "*", "@types/webidl-conversions": "*" } }, @@ -1829,12 +2277,19 @@ "tslib": "^2.3.1" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "dependencies": { - "follow-redirects": "^1.14.8" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -1911,6 +2366,11 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", @@ -2353,11 +2813,12 @@ } }, "node_modules/cmake-js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-6.3.0.tgz", - "integrity": "sha512-1uqTOmFt6BIqKlrX+39/aewU/JVhyZWDqwAL+6psToUwxj3yWPJiwxiZFmV0XdcoWmqGs7peZTxTbJtAcH8hxw==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-6.3.2.tgz", + "integrity": "sha512-7MfiQ/ijzeE2kO+WFB9bv4QP5Dn2yVaAP2acFJr4NIFy2hT4w6O4EpOTLNcohR5IPX7M4wNf/5taIqMj7UA9ug==", "dependencies": { "axios": "^0.21.1", + "bluebird": "^3", "debug": "^4", "fs-extra": "^5.0.0", "is-iojs": "^1.0.1", @@ -2580,6 +3041,17 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -2693,18 +3165,19 @@ } }, "node_modules/connect-mongo": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", - "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.1.0.tgz", + "integrity": "sha512-xT0vxQLqyqoUTxPLzlP9a/u+vir0zNkhiy9uAdHjSCcUUf7TS5b55Icw8lVyYFxfemP3Mf9gdwUOgeF3cxCAhw==", "dependencies": { "debug": "^4.3.1", "kruptein": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=12.9.0" }, "peerDependencies": { - "mongodb": "^4.1.0" + "express-session": "^1.17.1", + "mongodb": ">= 5.1.0 < 7" } }, "node_modules/connect-session-sequelize": { @@ -3222,6 +3695,14 @@ "dev": true, "peer": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -3254,9 +3735,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "engines": { "node": ">=8" } @@ -4036,9 +4517,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "funding": [ { "type": "individual", @@ -4080,6 +4561,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formidable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", @@ -4232,9 +4726,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" @@ -5472,8 +5966,7 @@ "node_modules/memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/memory-stream": { "version": "0.0.3", @@ -6091,60 +6584,93 @@ } }, "node_modules/mongodb": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", - "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "peer": true, "dependencies": { - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.5.4", - "socks": "^2.7.1" + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" }, "engines": { - "node": ">=12.9.0" + "node": ">=16.20.1" }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "saslprep": "^1.0.3" + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, "node_modules/mongodb-connection-string-url": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.4.tgz", - "integrity": "sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "peer": true, "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" } }, "node_modules/mongodb-connection-string-url/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "peer": true, "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "peer": true, "engines": { "node": ">=12" } }, "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "peer": true, "dependencies": { - "tr46": "^3.0.0", + "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=16" } }, "node_modules/mongodb-memory-server": { @@ -6186,6 +6712,16 @@ "node": ">=12.22.0" } }, + "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/mongodb-memory-server-core/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -6198,14 +6734,85 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", + "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", + "dev": true, + "dependencies": { + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dev": true, + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz", + "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==", + "peer": true, + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/mongoose": { - "version": "6.11.6", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.11.6.tgz", - "integrity": "sha512-CuVbeJrEbnxkPUNNFvXJhjVyqa5Ip7lkz6EJX6g7Lb3aFMTJ+LHOlUrncxzC3r20dqasaVIiwcA6Y5qC8PWQ7w==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.12.3.tgz", + "integrity": "sha512-MNJymaaXali7w7rHBxVUoQ3HzHHMk/7I/+yeeoSa4rUzdjZwIWQznBNvVgc0A8ghuJwsuIkb5LyLV6gSjGjWyQ==", "dependencies": { "bson": "^4.7.2", "kareem": "2.5.1", - "mongodb": "4.16.0", + "mongodb": "4.17.1", "mpath": "0.9.0", "mquery": "4.0.3", "ms": "2.1.3", @@ -6219,11 +6826,77 @@ "url": "https://opencollective.com/mongoose" } }, + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.1.tgz", + "integrity": "sha512-MBuyYiPUPRTqfH2dV0ya4dcr2E5N52ocBuZ8Sgg/M030nGF78v855B3Z27mZJnp8PxjnUquEnAtjOsphgMZOlQ==", + "dependencies": { + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=12.9.0" + }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0" + } + }, + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, "node_modules/mongoose/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/mongoose/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -7119,6 +7792,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -7129,9 +7807,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -7555,18 +8233,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -7753,119 +8419,46 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/sharp": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.30.7.tgz", - "integrity": "sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.1", - "node-addon-api": "^5.0.0", - "prebuild-install": "^7.1.1", - "semver": "^7.3.7", - "simple-get": "^4.0.1", - "tar-fs": "^2.1.1", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=12.13.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/sharp/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sharp/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sharp/node_modules/node-abi": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.33.0.tgz", - "integrity": "sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp/node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" - }, - "node_modules/sharp/node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "node_modules/sharp/node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/sharp": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", + "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", + "hasInstallScript": true, "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "semver": "^7.5.4" + }, + "engines": { + "libvips": ">=8.15.0", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.1", + "@img/sharp-darwin-x64": "0.33.1", + "@img/sharp-libvips-darwin-arm64": "1.0.0", + "@img/sharp-libvips-darwin-x64": "1.0.0", + "@img/sharp-libvips-linux-arm": "1.0.0", + "@img/sharp-libvips-linux-arm64": "1.0.0", + "@img/sharp-libvips-linux-s390x": "1.0.0", + "@img/sharp-libvips-linux-x64": "1.0.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", + "@img/sharp-libvips-linuxmusl-x64": "1.0.0", + "@img/sharp-linux-arm": "0.33.1", + "@img/sharp-linux-arm64": "0.33.1", + "@img/sharp-linux-s390x": "0.33.1", + "@img/sharp-linux-x64": "0.33.1", + "@img/sharp-linuxmusl-arm64": "0.33.1", + "@img/sharp-linuxmusl-x64": "0.33.1", + "@img/sharp-wasm32": "0.33.1", + "@img/sharp-win32-ia32": "0.33.1", + "@img/sharp-win32-x64": "0.33.1" } }, "node_modules/shebang-command": { @@ -7991,8 +8584,7 @@ "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "optional": true, + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", "dependencies": { "memory-pager": "^1.0.2" } @@ -8743,9 +9335,13 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -9767,6 +10363,15 @@ "js-tokens": "^4.0.0" } }, + "@emnapi/runtime": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", + "integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==", + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, "@eslint/eslintrc": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", @@ -9832,6 +10437,147 @@ "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", "dev": true }, + "@img/sharp-darwin-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", + "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", + "optional": true, + "requires": { + "@img/sharp-libvips-darwin-arm64": "1.0.0" + } + }, + "@img/sharp-darwin-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", + "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", + "optional": true, + "requires": { + "@img/sharp-libvips-darwin-x64": "1.0.0" + } + }, + "@img/sharp-libvips-darwin-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz", + "integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==", + "optional": true + }, + "@img/sharp-libvips-darwin-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz", + "integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==", + "optional": true + }, + "@img/sharp-libvips-linux-arm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz", + "integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==", + "optional": true + }, + "@img/sharp-libvips-linux-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz", + "integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==", + "optional": true + }, + "@img/sharp-libvips-linux-s390x": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz", + "integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==", + "optional": true + }, + "@img/sharp-libvips-linux-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz", + "integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==", + "optional": true + }, + "@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz", + "integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==", + "optional": true + }, + "@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz", + "integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==", + "optional": true + }, + "@img/sharp-linux-arm": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", + "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-arm": "1.0.0" + } + }, + "@img/sharp-linux-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", + "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-arm64": "1.0.0" + } + }, + "@img/sharp-linux-s390x": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", + "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-s390x": "1.0.0" + } + }, + "@img/sharp-linux-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", + "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-x64": "1.0.0" + } + }, + "@img/sharp-linuxmusl-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", + "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", + "optional": true, + "requires": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" + } + }, + "@img/sharp-linuxmusl-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", + "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", + "optional": true, + "requires": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.0" + } + }, + "@img/sharp-wasm32": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", + "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", + "optional": true, + "requires": { + "@emnapi/runtime": "^0.44.0" + } + }, + "@img/sharp-win32-ia32": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", + "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", + "optional": true + }, + "@img/sharp-win32-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", + "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", + "optional": true + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -9917,6 +10663,14 @@ "tar": "^6.1.11" } }, + "@mongodb-js/saslprep": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", + "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -10406,16 +11160,16 @@ "integrity": "sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==" }, "@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, "@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.3.tgz", + "integrity": "sha512-z1ELvMijRL1QmU7QuzDkeYXSF2+dXI0ITKoQsIoVKcNBOiK5RMmWy+pYYxJTHFt8vkpZe7UsvRErQwcxZkjoUw==", + "peer": true, "requires": { - "@types/node": "*", "@types/webidl-conversions": "*" } }, @@ -10642,12 +11396,19 @@ "tslib": "^2.3.1" } }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "requires": { - "follow-redirects": "^1.14.8" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "balanced-match": { @@ -10697,6 +11458,11 @@ "file-uri-to-path": "1.0.0" } }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", @@ -11036,11 +11802,12 @@ } }, "cmake-js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-6.3.0.tgz", - "integrity": "sha512-1uqTOmFt6BIqKlrX+39/aewU/JVhyZWDqwAL+6psToUwxj3yWPJiwxiZFmV0XdcoWmqGs7peZTxTbJtAcH8hxw==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-6.3.2.tgz", + "integrity": "sha512-7MfiQ/ijzeE2kO+WFB9bv4QP5Dn2yVaAP2acFJr4NIFy2hT4w6O4EpOTLNcohR5IPX7M4wNf/5taIqMj7UA9ug==", "requires": { "axios": "^0.21.1", + "bluebird": "^3", "debug": "^4", "fs-extra": "^5.0.0", "is-iojs": "^1.0.1", @@ -11242,6 +12009,14 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -11339,9 +12114,9 @@ } }, "connect-mongo": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", - "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.1.0.tgz", + "integrity": "sha512-xT0vxQLqyqoUTxPLzlP9a/u+vir0zNkhiy9uAdHjSCcUUf7TS5b55Icw8lVyYFxfemP3Mf9gdwUOgeF3cxCAhw==", "requires": { "debug": "^4.3.1", "kruptein": "^3.0.0" @@ -11728,6 +12503,11 @@ "dev": true, "peer": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -11750,9 +12530,9 @@ "dev": true }, "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" }, "detect-newline": { "version": "3.1.0", @@ -12380,9 +13160,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" }, "foreground-child": { "version": "3.1.1", @@ -12400,6 +13180,16 @@ } } }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "formidable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", @@ -12520,9 +13310,9 @@ "dev": true }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, "get-intrinsic": { @@ -13443,8 +14233,7 @@ "memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "memory-stream": { "version": "0.0.3", @@ -13900,45 +14689,56 @@ } }, "mongodb": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", - "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "peer": true, "requires": { - "@aws-sdk/credential-providers": "^3.186.0", - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.5.4", - "saslprep": "^1.0.3", - "socks": "^2.7.1" + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "dependencies": { + "bson": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz", + "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==", + "peer": true + } } }, "mongodb-connection-string-url": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.4.tgz", - "integrity": "sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "peer": true, "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" }, "dependencies": { "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "peer": true, "requires": { - "punycode": "^2.1.1" + "punycode": "^2.3.0" } }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "peer": true }, "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "peer": true, "requires": { - "tr46": "^3.0.0", + "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" } } @@ -13976,32 +14776,142 @@ "yauzl": "^2.10.0" }, "dependencies": { + "@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true + }, + "mongodb": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", + "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", + "dev": true, + "requires": { + "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + } + }, + "mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dev": true, + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } } } }, "mongoose": { - "version": "6.11.6", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.11.6.tgz", - "integrity": "sha512-CuVbeJrEbnxkPUNNFvXJhjVyqa5Ip7lkz6EJX6g7Lb3aFMTJ+LHOlUrncxzC3r20dqasaVIiwcA6Y5qC8PWQ7w==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.12.3.tgz", + "integrity": "sha512-MNJymaaXali7w7rHBxVUoQ3HzHHMk/7I/+yeeoSa4rUzdjZwIWQznBNvVgc0A8ghuJwsuIkb5LyLV6gSjGjWyQ==", "requires": { "bson": "^4.7.2", "kareem": "2.5.1", - "mongodb": "4.16.0", + "mongodb": "4.17.1", "mpath": "0.9.0", "mquery": "4.0.3", "ms": "2.1.3", "sift": "16.0.1" }, "dependencies": { + "@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "mongodb": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.1.tgz", + "integrity": "sha512-MBuyYiPUPRTqfH2dV0ya4dcr2E5N52ocBuZ8Sgg/M030nGF78v855B3Z27mZJnp8PxjnUquEnAtjOsphgMZOlQ==", + "requires": { + "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + } + }, + "mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } } } }, @@ -14679,6 +15589,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -14689,9 +15604,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "q": { "version": "1.5.1", @@ -15002,15 +15917,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -15150,75 +16056,32 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "sharp": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.30.7.tgz", - "integrity": "sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==", - "requires": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", + "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", + "requires": { + "@img/sharp-darwin-arm64": "0.33.1", + "@img/sharp-darwin-x64": "0.33.1", + "@img/sharp-libvips-darwin-arm64": "1.0.0", + "@img/sharp-libvips-darwin-x64": "1.0.0", + "@img/sharp-libvips-linux-arm": "1.0.0", + "@img/sharp-libvips-linux-arm64": "1.0.0", + "@img/sharp-libvips-linux-s390x": "1.0.0", + "@img/sharp-libvips-linux-x64": "1.0.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", + "@img/sharp-libvips-linuxmusl-x64": "1.0.0", + "@img/sharp-linux-arm": "0.33.1", + "@img/sharp-linux-arm64": "0.33.1", + "@img/sharp-linux-s390x": "0.33.1", + "@img/sharp-linux-x64": "0.33.1", + "@img/sharp-linuxmusl-arm64": "0.33.1", + "@img/sharp-linuxmusl-x64": "0.33.1", + "@img/sharp-wasm32": "0.33.1", + "@img/sharp-win32-ia32": "0.33.1", + "@img/sharp-win32-x64": "0.33.1", "color": "^4.2.3", - "detect-libc": "^2.0.1", - "node-addon-api": "^5.0.0", - "prebuild-install": "^7.1.1", - "semver": "^7.3.7", - "simple-get": "^4.0.1", - "tar-fs": "^2.1.1", - "tunnel-agent": "^0.6.0" - }, - "dependencies": { - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "node-abi": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.33.0.tgz", - "integrity": "sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==", - "requires": { - "semver": "^7.3.5" - } - }, - "node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" - }, - "prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "requires": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - } - }, - "simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - } + "detect-libc": "^2.0.2", + "semver": "^7.5.4" } }, "shebang-command": { @@ -15310,8 +16173,7 @@ "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "optional": true, + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", "requires": { "memory-pager": "^1.0.2" } @@ -15912,9 +16774,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" }, "v8-compile-cache": { "version": "2.3.0", diff --git a/package.json b/package.json index bfef97f7..65459e55 100644 --- a/package.json +++ b/package.json @@ -57,13 +57,13 @@ "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", "archiver": "^5.3.1", - "axios": "^0.26.1", + "axios": "^1.6.2", "bcrypt": "^5.0.1", "body-parser": "^1.20.0", "colorette": "^2.0.20", "commander": "^10.0.1", "compression": "^1.7.4", - "connect-mongo": "^4.6.0", + "connect-mongo": "^5.1.0", "connect-session-sequelize": "^7.1.7", "cookie-parser": "^1.4.6", "cors": "^2.8.5", @@ -98,9 +98,9 @@ "request-multipart": "^1.0.0", "run-script-os": "^1.1.6", "sequelize": "^6.32.1", - "sharp": "^0.30.4", + "sharp": "^0.33.1", "shorthash2": "^1.0.3", - "uuid": "^9.0.0", + "uuid": "^9.0.1", "ws": "^8.13.0" }, "devDependencies": { From 6d05073ef7ab5b0f239e55c263d9b22efe34f2ea Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 23 Dec 2023 21:36:13 +0800 Subject: [PATCH 269/365] feat(sql): implement create patient API --- .../PAM-RS/service/create-patient.service.js | 29 +++++++++++++++++++ .../controller/PAM-RS/create-patient.js | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js diff --git a/api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js b/api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js new file mode 100644 index 00000000..8c142725 --- /dev/null +++ b/api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js @@ -0,0 +1,29 @@ +const { PatientPersistentObject } = require("@models/sql/po/patient.po"); +const { CreatePatientService } = require("@root/api/dicom-web/controller/PAM-RS/service/create-patient.service"); +const { set } = require("lodash"); +const shortHash = require("shorthash2"); +const { v4: uuidV4 } = require("uuid"); + +class SqlCreatePatientService extends CreatePatientService { + constructor(req, res) { + super(req, res); + } + + async create() { + + let incomingPatient = this.request.body; + let patientID = shortHash(uuidV4()); + set(incomingPatient, "patientID", patientID); + set(incomingPatient, "00100020.Value", [ + patientID + ]); + + const patient = new PatientPersistentObject(incomingPatient); + await patient.createPatient(); + return { + patientID + }; + } +} + +module.exports.CreatePatientService = SqlCreatePatientService; \ No newline at end of file diff --git a/api/dicom-web/controller/PAM-RS/create-patient.js b/api/dicom-web/controller/PAM-RS/create-patient.js index b7a2a421..b1e33e77 100644 --- a/api/dicom-web/controller/PAM-RS/create-patient.js +++ b/api/dicom-web/controller/PAM-RS/create-patient.js @@ -1,6 +1,6 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { CreatePatientService } = require("./service/create-patient.service"); +const { CreatePatientService } = require("@api/dicom-web/controller/PAM-RS/service/create-patient.service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class CreatePatientController extends Controller { From 0177d69a14a378ee011bc176a97e7075bec52d5f Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 24 Dec 2023 09:59:44 +0800 Subject: [PATCH 270/365] feat(sql): add update patient API --- .../PAM-RS/service/update-patient.service.js | 17 +++++++++++++++++ .../controller/PAM-RS/update-patient.js | 3 +-- models/sql/models/patient.model.js | 4 ++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js diff --git a/api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js b/api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js new file mode 100644 index 00000000..e080929c --- /dev/null +++ b/api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js @@ -0,0 +1,17 @@ +const { PatientPersistentObject } = require("@models/sql/po/patient.po"); +const { UpdatePatientService } = require("@root/api/dicom-web/controller/PAM-RS/service/update-patient.service"); + + +class SqlUpdatePatientService extends UpdatePatientService { + constructor(req, res) { + super(req, res); + } + + async update() { + let patientPO = new PatientPersistentObject(this.incomingPatient); + + return await patientPO.createPatient(); + } +} + +module.exports.UpdatePatientService = SqlUpdatePatientService; \ No newline at end of file diff --git a/api/dicom-web/controller/PAM-RS/update-patient.js b/api/dicom-web/controller/PAM-RS/update-patient.js index fffb657b..4bbaa0f2 100644 --- a/api/dicom-web/controller/PAM-RS/update-patient.js +++ b/api/dicom-web/controller/PAM-RS/update-patient.js @@ -1,8 +1,7 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { CreatePatientService } = require("./service/create-patient.service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); -const { UpdatePatientService } = require("./service/update-patient.service"); +const { UpdatePatientService } = require("@api/dicom-web/controller/PAM-RS/service/update-patient.service"); class UpdatePatientController extends Controller { constructor(req, res) { diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index a262a867..704e7239 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -19,6 +19,10 @@ class PatientModel extends Model { return bringPatient; } + + toDicomJson() { + return this.json; + } }; PatientModel.init({ From e93603f6aed494974e40e9c9c3fb061a8d98beb3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 24 Dec 2023 16:11:13 +0800 Subject: [PATCH 271/365] feat(sql): implement delete patient API --- .../PAM-RS/service/delete-patient.service.js | 27 +++++++++++++ .../controller/PAM-RS/delete-patient.js | 2 +- models/sql/deleteSchedule.js | 39 +++++++++++++++++++ models/sql/models/patient.model.js | 9 +++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js diff --git a/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js b/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js new file mode 100644 index 00000000..a79b3de4 --- /dev/null +++ b/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js @@ -0,0 +1,27 @@ +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { PatientModel } = require("@models/sql/models/patient.model"); +const { DeletePatientService } = require("@root/api/dicom-web/controller/PAM-RS/service/delete-patient.service"); + +class SqlDeletePatientService extends DeletePatientService { + constructor(req, res) { + super(req, res); + } + + async delete() { + let { patientID } = this.request.params; + let patient = await PatientModel.findOne({ + "x00100020": patientID + }); + if (!patient) { + throw new DicomWebServiceError( + DicomWebStatusCodes.NoSuchObjectInstance, + `Patient "${this.request.params.patientID}" does not exist.`, + 404 + ); + } + + await patient.incrementDeleteStatus(); + } +} + +module.exports.DeletePatientService = SqlDeletePatientService; \ No newline at end of file diff --git a/api/dicom-web/controller/PAM-RS/delete-patient.js b/api/dicom-web/controller/PAM-RS/delete-patient.js index b7e6f6af..cc3bf3e5 100644 --- a/api/dicom-web/controller/PAM-RS/delete-patient.js +++ b/api/dicom-web/controller/PAM-RS/delete-patient.js @@ -1,7 +1,7 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); -const { DeletePatientService } = require("./service/delete-patient.service"); +const { DeletePatientService } = require("@api/dicom-web/controller/PAM-RS/service/delete-patient.service"); class DeletePatientController extends Controller { constructor(req, res) { diff --git a/models/sql/deleteSchedule.js b/models/sql/deleteSchedule.js index 81cadb73..a04f03ff 100644 --- a/models/sql/deleteSchedule.js +++ b/models/sql/deleteSchedule.js @@ -5,9 +5,13 @@ const moment = require("moment"); const { logger } = require("@root/utils/logs/log"); const { InstanceModel } = require("./models/instance.model"); const { SeriesModel } = require("./models/series.model"); +const { PatientModel } = require("./models/patient.model"); // Delete dicom with delete status >= 2 schedule.scheduleJob("0 0 */1 * * *", async function () { + deleteExpirePatient().catch((e) => { + logger.error(e); + }); deleteExpireStudies().catch((e) => { logger.error(e); }); @@ -19,6 +23,41 @@ schedule.scheduleJob("0 0 */1 * * *", async function () { }); }); +async function deleteExpirePatient() { + let deletedPatients = await PatientModel.findAll({ + where: { + deleteStatus: { + [Op.gte]: 2 + } + } + }); + + for (let deletedPatient of deletedPatients) { + let updateAtDate = moment(deletedPatient.getDataValue("updatedAt")); + let now = moment(); + let diff = now.diff(updateAtDate, "days"); + if (diff >= 30) { + let patientID = deletedPatient.getDataValue("x00100020"); + + logger.info("delete expired patient: " + patientID); + await Promise.all([ + InstanceModel.destroy({ + where: { + x00100020: patientID + } + }), + SeriesModel.destroy({ + where: { + x00100020: patientID + } + }), + deletedPatient.destroy() + ]); + + await deletedPatient.deleteStudyFolder(); + } + } +} async function deleteExpireStudies() { let deletedStudies = await StudyModel.findAll({ diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index 704e7239..32bb66c5 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -20,6 +20,12 @@ class PatientModel extends Model { return bringPatient; } + async incrementDeleteStatus() { + let deleteStatus = this.getDataValue("deleteStatus"); + this.setDataValue("deleteStatus", deleteStatus + 1); + await this.save(); + } + toDicomJson() { return this.json; } @@ -60,6 +66,9 @@ PatientModel.init({ }, "json": { type: vrTypeMapping.JSON + }, + "deleteStatus": { + type: DataTypes.INTEGER } }, { sequelize: sequelizeInstance, From 274eb206bd8f410d0795fcbb98853e3553a2ca07 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 24 Dec 2023 16:24:29 +0800 Subject: [PATCH 272/365] fix(sql): passing wrong find options in delete patient service - missing `where` property to wrap queries --- .../controller/PAM-RS/service/delete-patient.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js b/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js index a79b3de4..6efc8ad4 100644 --- a/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js +++ b/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js @@ -10,7 +10,9 @@ class SqlDeletePatientService extends DeletePatientService { async delete() { let { patientID } = this.request.params; let patient = await PatientModel.findOne({ - "x00100020": patientID + where: { + "x00100020": patientID + } }); if (!patient) { throw new DicomWebServiceError( From 17545b222dfb20ec2068413768086ea2602c13c2 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 24 Dec 2023 16:27:41 +0800 Subject: [PATCH 273/365] refactor(sql): Add base DICOM model hierarchy to use duplicate functions --- models/sql/models/baseDicom.model.js | 27 +++++++++++++++++++++++++++ models/sql/models/instance.model.js | 21 ++------------------- models/sql/models/patient.model.js | 26 ++------------------------ models/sql/models/series.model.js | 22 ++-------------------- models/sql/models/study.model.js | 22 ++-------------------- 5 files changed, 35 insertions(+), 83 deletions(-) create mode 100644 models/sql/models/baseDicom.model.js diff --git a/models/sql/models/baseDicom.model.js b/models/sql/models/baseDicom.model.js new file mode 100644 index 00000000..19243f84 --- /dev/null +++ b/models/sql/models/baseDicom.model.js @@ -0,0 +1,27 @@ +const { raccoonConfig } = require("@root/config-class"); +const { Model } = require("sequelize"); + +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} +class BaseDicomModel extends Model { + async getAttributes() { + let obj = this.toJSON(); + let jsonStr = JSON.stringify(obj.json); + return await Common.getAttributesFromJsonString(jsonStr); + } + + async incrementDeleteStatus() { + let deleteStatus = this.getDataValue("deleteStatus"); + this.setDataValue("deleteStatus", deleteStatus + 1); + await this.save(); + } + + toDicomJson() { + return this.json; + } +}; + +module.exports.BaseDicomModel = BaseDicomModel; \ No newline at end of file diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js index 251bf99f..11711d46 100644 --- a/models/sql/models/instance.model.js +++ b/models/sql/models/instance.model.js @@ -9,19 +9,9 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPath } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); +const { BaseDicomModel } = require("./baseDicom.model"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -class InstanceModel extends Model { - async incrementDeleteStatus() { - let deleteStatus = this.getDataValue("deleteStatus"); - this.setDataValue("deleteStatus", deleteStatus + 1); - await this.save(); - } +class InstanceModel extends BaseDicomModel { async deleteInstance() { let instancePath = this.getDataValue("instancePath"); @@ -247,11 +237,4 @@ InstanceModel.getInstanceOfMedianIndex = async function (query) { return instance; }; -InstanceModel.prototype.getAttributes = async function () { - let seriesObj = this.toJSON(); - - let jsonStr = JSON.stringify(seriesObj.json); - return await Common.getAttributesFromJsonString(jsonStr); -}; - module.exports.InstanceModel = InstanceModel; diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js index 32bb66c5..a6d54bc2 100644 --- a/models/sql/models/patient.model.js +++ b/models/sql/models/patient.model.js @@ -3,14 +3,9 @@ const sequelizeInstance = require("@models/sql/instance"); const { vrTypeMapping } = require("../vrTypeMapping"); const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); const { raccoonConfig } = require("@root/config-class"); +const { BaseDicomModel } = require("./baseDicom.model"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -class PatientModel extends Model { +class PatientModel extends BaseDicomModel { static async updateOrCreatePatient(patient) { /** @type {PatientModel | null} */ const { PatientPersistentObject } = require("../po/patient.po"); @@ -19,16 +14,6 @@ class PatientModel extends Model { return bringPatient; } - - async incrementDeleteStatus() { - let deleteStatus = this.getDataValue("deleteStatus"); - this.setDataValue("deleteStatus", deleteStatus + 1); - await this.save(); - } - - toDicomJson() { - return this.json; - } }; PatientModel.init({ @@ -95,11 +80,4 @@ PatientModel.getDicomJson = async function (queryOptions) { })); }; -PatientModel.prototype.getAttributes = async function () { - let patientObj = this.toJSON(); - - let jsonStr = JSON.stringify(patientObj.json); - return await Common.getAttributesFromJsonString(jsonStr); -}; - module.exports.PatientModel = PatientModel; diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js index fcb74252..406d0424 100644 --- a/models/sql/models/series.model.js +++ b/models/sql/models/series.model.js @@ -9,24 +9,13 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); +const { BaseDicomModel } = require("./baseDicom.model"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -class SeriesModel extends Model { +class SeriesModel extends BaseDicomModel { getSeriesPath() { return this.getDataValue("seriesPath"); } - async incrementDeleteStatus() { - let deleteStatus = this.getDataValue("deleteStatus"); - this.setDataValue("deleteStatus", deleteStatus + 1); - await this.save(); - } - async deleteSeriesFolder() { let seriesPath = this.getDataValue("seriesPath"); logger.warn("Permanently delete series folder: " + seriesPath); @@ -175,11 +164,4 @@ SeriesModel.getPathGroupOfInstances = async function(iParam) { } }; -SeriesModel.prototype.getAttributes = async function () { - let seriesObj = this.toJSON(); - - let jsonStr = JSON.stringify(seriesObj.json); - return await Common.getAttributesFromJsonString(jsonStr); -}; - module.exports.SeriesModel = SeriesModel; diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js index 62c623d1..1478f0dd 100644 --- a/models/sql/models/study.model.js +++ b/models/sql/models/study.model.js @@ -11,14 +11,9 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); const { logger } = require("@root/utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); +const { BaseDicomModel } = require("./baseDicom.model"); -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -class StudyModel extends Model { +class StudyModel extends BaseDicomModel { async getNumberOfStudyRelatedSeries() { let count = await SeriesModel.count({ where: { @@ -37,12 +32,6 @@ class StudyModel extends Model { return count; } - async incrementDeleteStatus() { - let deleteStatus = this.getDataValue("deleteStatus"); - this.setDataValue("deleteStatus", deleteStatus + 1); - await this.save(); - } - async deleteStudyFolder() { let studyPath = this.getDataValue("studyPath"); logger.warn("Permanently delete study folder: " + studyPath); @@ -218,11 +207,4 @@ StudyModel.getPathGroupOfInstances = async function (iParam) { } }; -StudyModel.prototype.getAttributes = async function () { - let studyObj = this.toJSON(); - - let jsonStr = JSON.stringify(studyObj.json); - return await Common.getAttributesFromJsonString(jsonStr); -}; - module.exports.StudyModel = StudyModel; From ec8e658c113d766ba696e91735b7f9ffb755d8bc Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 29 Dec 2023 14:03:16 +0800 Subject: [PATCH 274/365] feat: add constructor for injecting CFindSCP without qr levels --- .../dcm4chee/lib/qrscp/dcm777-5.29.2.jar | Bin 25811 -> 25930 bytes .../dcm777/net/BasicModCFindSCP.d.ts | 39 ++++++++++++++++-- .../dcm777/net/BasicModCFindSCP.d.ts.map | 2 +- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/models/DICOM/dcm4che/javaNode/dcm4chee/lib/qrscp/dcm777-5.29.2.jar b/models/DICOM/dcm4che/javaNode/dcm4chee/lib/qrscp/dcm777-5.29.2.jar index 6c38f0e0e2ef92606c86025f04e27290740fd34c..b7d6f2fef1089f7a01710a0e3927a636777242d0 100644 GIT binary patch delta 5305 zcmaJ_2UJr_w}#NAcS7h@2pvNrO({~u(0i4pRHaHMBB6+YkOOidQUz2{dNZIRQAz|w zP|?sq6i}KpMS&NB|9zMLy>-u8S$ocRzWwbzbLPy<&Ttxis`j@Q(jy zzKZ99a26mNfGD(0-*XeNi427mBA}i_#ncBs=qs}tHiiv zj)SjHdZWpsp1QbZc`M}gjgPg$t71%&*g%&NrRe7ixr&`U_JfJ^uso-nYyv^ouF57^>*^HS z@h#uv{_{;sAGdQpcK3gvt*}BM^2BZiO1~Di$^~y@k|N~xBsl49Fvp{EtiVz|f;W~! zN@X__=GfumcGYpIf4LXlL1vEy-!-Zkj)T)Ld_W;Kt`sjQ1*Tg2*xoUEkkmYGNadUJ zlv2mDrPMZF9$Ia+T-31-7ahvpzir+go?Q;&%t`(z{@FED+!bj#Ktu>-q{pzwsfAqr z?KgX7i|_#rZL{M?r|~vXm!5ZAU-dS58pjuwxNF)9vUE7h^Iq2TyDDp!O>%fzSiv)0 zt}qngsYJji^Va2er)xhZYjrtR`(?%t&=bA35Ow90)f@3K&aOHwP2=uK4L`ZoHfGL&c!Gs4gs2s?4f$BWjo};b-ebj3FP2B?`-*?j~lkE;2 zf^1>*dSn|zf0k@}=`E<3A%%?s4C=IGz=73@4AipfQyw&cVhsf#%ZVLeab`iiv-@ag zWyrUWG-_j0@P1=%32%}WO|`JFxMs`qaHQclCD+oGN8qm9PZL4B=$b3_*d@R$Ztb@A zNB9!RZ>dyKD{g*rXj{Aa?&KJC7PUEXIFv{wt@`sbcCvN%Gq$f5wO2CuqDtBw>V!}Pw-P)=ziri_Bna>ei$|xfAKbGpnUOOy=h7;I8^XbPfTsq zuwQ@pM%V+pR>o$=hiVx)cbMiluBph1s0wf|%8cg+^>`4`m&zB4Ln%>%@83%$%e;Kw zP~Veir}FWH-a8o43ul^#E&3)}%=f`NVK+Tp)>_qCBFeFF$rMrf!WYTz->jnMu9}#W zzZ3@G@L8eT3rjJhT(hzxx7*O_$;sT)i&As0s;X((;e}|=U>K*qRu@MRP*39o7W3zE zP!2GJotW_VlRb%atwgnj8khmw@3JeM3mDvtW^~YXE`t~7^pIY21x9P8-!Q8k>Ej>p zK~u&G%eGANOHw~WWaDw5vDu)mVrp#Li>Azv(gR{S}7M zHC@HizFF?j#t)y|pd}3BytwnP_aQ`vELAk`(NF2AIMM{Y@I-ZTWPZB;h`J(;(9^NA z9Xx7lH8v3!UGR!<^{Y9BvoZ!}*QV0MKFMM|g&;~k_YRw)#;-jcP))wrEB^YeNbC3s z2Hl^SyEfw7cOEPlq%}xP(>TgzqKuHaC6u{3VWm#GH`9j^_`2}N=ah3ee}xQo6%APj zSm;OECX02A`tqY(G%Fx_xBNcLj&U5*I=g*(QmlGO#8tNztqtB42?=y?ANLn#2)`Y1 z7m1}89t`muS809B9o{eARu`+&T~gQa;>33*0>m*SK{}DIs}9W$<_*4{`6h&3kF$*E z)mtg+1^%5KDwdE3Zc&8Fyxo_3IiRi?6>Tu<@aCCBM}Hlvx^32SN5N5jAwtTAc%CMh zrZ$aphhI{oVU3n3FaS74aTFLhbebR8cNmR@H4 zQYA4(uqjz729y4w-p-W)roV(PQ8kp?Ee@dfF>p@yoTx^bv{C_{fjP|@O6(PNS1*N9 z z#?_Tu^Lhg7r4?ImHPuI{bh%hGpWo0%@3ID663kuK9@7bpS>TVx+?9@so*pWmcbW^d zzA@cRC9y0rC=`i?-jUy4BWR_S9b@RXlxU0`hCJ=oicNVFd#3w7ZKqILw4$%UJzbg3v!$Lm?{n>eAfBGFL{EO}9?SC*}1Em)}&~+>3ucY8}o#?9!1K zao=Oa_pwj~s>DJ!4{biTGE=(kI!nWFhe=a-u&P)|yZo_ml+7Mrab)Zk`&Zo`8`git z?lTpO|K>f|vTF8bq&aCRC~mX-bIbbQjf4RRa^`7_<}INkZ#RO%rsQ2lSVaXW5#~d= zgbo{UX??w!%9ju-(>{J`C?f1( z4HourhS>FhJH$T`f!3j6;7%!3asD3U-!&c(;L}~H^mSu)%(EarKU zx?6W%dnVaLX|4$I>n<0UfMoflgd&`$tR#ZAV1?F>R1G0 zN-zk2%)q)l7^CymiUMEaJ;CF|;^uJShl~>opL3YNOEw%-u8~b(yhg1lMlGUkfslpq z#{Iqz;gB)L?NsBc{mu>h3?P4i(cSzwh3k)0e8o|I*-t?4mBG;-7aa+xj4y6IiT`>(W&b zQTS^1H3x3^TChFtd$?R6d{{8-;e}y_+MY!F5YaPAM*XeUIYHQCao#n~&Mx+ja<{;* zsDm~1vu2{kELN|l(Zmw6j)|a#2|j}(Rg7kHL3g!GAR)d353QuQEmXr>-9wWt&BrtL z7DHP)*`N+KAd_23eY_AS$e8Op_~Kht>lU9gj>Cb|TCM7y%atNgcF`-0s8u(EkFV8Js8GR)@ znTdJwveQcFP2r%}vhxEOztQu(``Z~QqOO~2XOjWCz*1_zQw-)2wLR7`H-9P^pX=9x zMXvkXu4WxGu2hr@bng?ksOt+_JI#5C5ieH!De+WFmhXMXOG2i6MZ1by6E$#SDKN7| z#jIGK$#O!iY(TR(m8>lurS_v^)i1Q|7vqz8a7Hk3UOenp#-MQFm(JDvSlQv+%%U)x z)x?OlitP4{6HxJlgx}8@>n6RviMen#M4-in?J@G;q!@czFMk^DuGKnQC;OKZ(;sL1 z%s6c0XHMpfTs52Gkr@m=b@RIm?KPL9I?&X&7vWX>Unq zj@>l1CCdH0|HUZU+QJwnf$ygGyRC^ZA-*=o#fIX{^0pR+^tUVemE>J+%xE`{ddh?- z62Au*?#zE0#cZHs`ATAB_n8RKKfUz{II(xEhi)*?i>D_G5W9fojAfV6?6kuiPpVzZ zNH?8!ij{tBtkaX6gDSkH^AJ(>Hq%1$E>!TGlWrLQ^i1u3E|E#NB`Vo;-G4)I_1>9)rc|pt%4MdXr7K;h3g=DIBdpw(OO|uvuVVb>nUX%RvsGz~vLD$VEWAlKUYdtYUkJM8aIi z2-!){ox4#O4*n0I+IR_}a7ZiGa5_X;WK@7UByA&1E0vlo*{Sn_+#m@c8aoZzX~}3j zGN0^7LRA})7D9*4ojR((qz!XpjpZ+LM;ag4kYo?ao?~_hW|Fo0yFdcW>|o9Z2nFdP zRZQfI^^ZL?`cxDY$TQYD^2VkHih=G|ye}!5n^1zdDE|Hz;fS-`5$8l;%1(nETF4$k zIZ3)x1bpo|$q33G0@9@hrtK5}g1soI`~sHj1wiYEh&l}m>fgdp9|_YzD)cUgT>RI> z7>{_Y{_(iz8|D-4@&7sk-3~mIP(aiX4E?uE1dr$jllXGv?wLD6D4&q-M1ToLI=O!; zkal&uw&1aZNzybNC#9uyM1g|=n1%|IoP-Z#FisFkGa$%G=12|)3O5+rU__Fq2qgu@ zsUr#%B)+N00q38m3e6*&Z)`xYlM<;q`Jf#C4b;r63ly?)MW`^)kT<` z^<#Pvfr8YlGSctF5!FZ;GSSLah#Y<;j`(_?L?|Z_VMmBWAj?&sET($DZj+MqT$VJu z)sAp9_EC32RZFh5-h3w2NuM59Fm#OLqOM6$SCr> z7&*|F(3j(zqyRpoU|L54&A|Q;*#I1j=|2+VBne$O`H7&rC^?pnI|NjL0M*jhIHyQV*BsG`3|gloFPYdX5VZ_kk)|PB;L^hoJW!bWq^232sy@WPY4KY zK~8AgQ{(``ctJoPEXfFIJpxR)`lE4VquC(Ak#{J*ty7xTKJnuR0_xruuIp6nw-@JN7G15mtXM(0Y!2*Ipp`a7$+{tvx zl!roJn1J@4lRDM2t{V~)+fkXu^i_j8sxoGEevrn$YIx_dFaT>CeKhi+Fv<7@PIE-4 zBG`N>ZRaIRLP~-Pg_l=_`Si`2AB9F8X&J5i-B0ubnri$l zR|;$iKef$2q`E3{TXlc*Rn924k-(~_NVS~%B4CSb+k~idNdzFUS(vC=%PJZbL|d%W z$_RIPbl^yRKBwH>tYJB5)y;>sfyq+U!#~W0d{^y<7V?A~FKhB;m6>tZ^gH#mAg$NS zr7Xd6s%xCzJ#U3-#%Eq_z7khacK%lfq8v9zm>JWmEvgnYLvs|{Bw?Ev?)HLZ`{uA? zJ7ft8dUxNw-*j`iAHQ3bJhADK zz+OAnEo7SlseoqmaT^NES%|W=q%9{I)2}$aa2-=Whwe&AdtI^niqa=b3((Bwq(Hb-ne1|GV;V-MOWZScO-P5nPKot$2-3w0l}}A2og`jX#c)q_=7~ z12bf3%Jm2ov8I0`IYtPm=;)$JW0Lpud57i&xueY@A20tgeI*)-7KN$NbW9 zP1lH&bQ=wpUyiT2NW3F!^np&b0N4NgJ!ObTNchCJ>v-oG{GQi?i668kXbALLtO&6k z3<7)gsGV zP<^uOhniACRJn;f)XFEx3VQ}~Mv?*p`p2aPNLwL@GYo7Llwgw2AEyin0~k9DGlpf~ zNS6Z|ArhM%Ot~yby)?rr=oVGrckKikr0uaR%p;wI5jmq-+By~R8 z;)>@Zs=A8zhP8On=p~5}WXlMQ&@IzHw<1R?VJtot&|F)SlkGZAcu&);-GPx%n*1Z3 zJ+s8Ke}2Rvs#K#-bm~l(>=J&`(8bH$JbMm*#i1|LtJJS_tZIqSt1SK6BuCCS{mYL| z;fbx_(&+~7C-zd6e*|ZjeoLtFu%N9NIZ!a1t@qpzseGuRbDE2D?S+GVOQ#3+DH{y# za=}Er#1JVk3cVViK(M_~@d;Vh_3L-LNWjU4A(S7c7@?q6tyH$#S^&e(m-S!A-o2Ur z6(ncdnu$(Mvu1h|4eJPOu~G#|ZM@?1F=#^{Q?B$cj#L5f|8`!d9){irqN%J|K$ z4Mtv_`r(<6^{M4q*sSW>1GB-;_QGms*hkhMfgkN%VNUY2U!rAjYT_>Yr2Y^gO5+dz=92@B+9LZzW1_@(U@|#NmF0s zoU7xiOPF6k1p7{^xYF#r?_tim0n!BGicSk_|MEj#ijay%#vW?sMaz2+V&Iu+){F~j-=e({+77VXxwH)gitiqiV~;yJW4C!8mv%w zmp6WCF-2+Mo8WeOAokC2w%7LeHWPavxx1;ezGQdVrk-i~dfVgrowd;&WtfwRX8P>s z7kOF8XmxYn(}j7tMMm5+wfPUMt|r!*q;*)jX@s)yKNSiiNc`}ua^aspZ~wp}{#D6# zE69?$-&vcBr}ruEoLfW9nLL>qjf=!d={0DCgWMG1k$r?0ORKzh3F z^geU~_%n7T2*U;mj=C^(LEAHrH2O99dOmSemf1LY-d4N$s*Gs$+@d?XuV3ho4EuzD zk!<)pu$1UNYtL$Y83Wb&v(~Gz_{$$Z#(vb(7=+PfMYb-NuJV=@F8}B@_&9J+f50mL zPubA9A}iRJn#i~P{8g_@LaStBi)|(XZaQR(4WwlZYY;Z+BdTrL0)~SI{JIvr%NKO( zhdphy2{f2OQ~P28xj3|}^&(BF+yyqjdsk~1T5Gf@YhD0pA3XIMSJh6@_V7Jgim}eW zmVRX@^;&I~wlC63C4NBdc zHZ_%*m_LE)QR9r^ijLyF9VhSG6XBuT74!nhF)X@#K(=}u5MwK#o6dgyJ3=qrx!zfA zmIrYu)1N+W3|R^RnPIX1Z`P%RSgwp>j}jN;L-^Ae`=AI5#wjI})2p|KK> zCtU0tx$&LRYg(&BW{agOnUY}^Rq&ik%0TTUL*VWFS;_ld)*F*rzwmv0My>#&73g$_ zZ$9Or+4M=sb9F1v>*2lZ!+}}YX*N1Q-W@SYu5Cyf44Wuv)@rKL`%bsm+C#lHs@H0k z*;PT9xdzcUkjPs_c{wX9Mg+Z{(Td}ZbforF=z4$q*Dgo?px*rl+0BN{j9Zo|0$V2O zs&q>gKP6WHDZav%{piTIJ3&k#?y4b-ql>3Q^gL2gex_^zZ#(xX4!aJ9@w@r}6$tbv z-9Nj|v8Dh}GKv#x*c&0_o^gf8=&*?ZPk8hRb{M!N%RapxM~V>~OciHny5b^8q1;&D z7BMP&TZfV*+^ynC-)GtTOjn$)w1-1@LbfrxYg=tiw&P9d`yfJU|9N3{Eg{*hLCBc? zl{YT=>%VqX@jFU{y}e)gpes(=gAupG3*t@%b9!0(+lX;C%XG`l_+17w3 zH1Q{_q)4HoRzjnvm7Hk;@|+bnr4Fps1{>nM(i_4(Xo>fYyv{D*7QFMJ{NcVd7OY== z!e-NZtk~B48_GE++N%eXe3JKKUotx6q(U0xW|eJ z54etQ^JE>#yxQXtucVc_fxOF@N9aS^8{ga*=r6O9!3Hr9X{Iro6l+om0hijYCzrD^ zOtCsNPIB#~XwT*w-z>-GvJSd?r3Y3}g-5=g)uQ@#c5SZMXzkUi^;Fd}xPOi9$m0E^ z2ag0)1T-=;*DHFlVN9W+()k~IYiciV!Em=G*%(U#sd>b9FKE-}BRfNSEeM=+KW+VH zE4bb1-nyHz4Si+{GCSpB9#AFk+WYg}K(4sj*AZA-wI$y}H;2rS(k8plJJAl+gT3n0 zMRD~N6Ya}mkchQLM9|2T@$DTTvYaomA){s?alUlhDF&A?t1`2nZUfvv>Y4A3on5EbM9##5>RgMfz`SU519F(^Gp@`Ar5qqs( z3#a$)7@Ft@2l32APhnAA9m~{(lpdEtlwXzdI}sZ7w#xC7j!X_-^bG_;fI^Epj(fU` zd3$}+%h_qyU}tN)q>A@EelsFKur$kMX#kDpGBiX%@O++_1sg3+p?=2dSLyZbq16Mi&!AC$Gm4NZ#WzJWgLbd_^BKo8_Hvb9pzA`)G<(NLc_3lBl3IxMR!nEAuB-J_gLRCIR) zgNH-wBsz1bhg8N{wwQ`}e~+gQiHS0qwHpl7i}H@Ap7PX>)t@8WzQ$S_d2*Bgr?=MV zox93D*JO1^hekU`eeJ2AzYJ9Q(}LDdVI4jy&EuC8AXEMIJ;k4>`rXS59DQkbaBk*3 zf!~d;%kF{~KiyO6o|80WWfz$9G?OvJOOF%@OoxglOp(mA#!nPO-VXeSn zin0$#yK@M%gCbdqb>jpGl=$z36Q~tOrZ}(@BLwrnEJi7UM z_UHykHbnx7j;zFc=io;pwc=rv|JjzSlD6dnz>GK}5T^hmhDkG#v7}WP35YtMB7Q)! zP?GJh%UwDeIW(K<%t}mG;QYTr2DpT!Cw@~zkfEfly#z3$$P9R3VSu+44bc;I;s_Zf ziUihiCyCYPV^5HcDrk0*Z%DDq02??eqQb@IljPiv6f8uPk=c>|WK+{4BMO=_0NjO+ zC}{qRTm~Y)l^q4SgM?ue=}5;J%-;tdpc^Ac9@)phDKr2s>oeph6>B)fZQzl$B=FQ0 z#&gIxJf@INNaRcAFL@*;NjOb1Cd5r~K$E1@BmFdvVbp*y8)d-2hK&bII`fP!u2?&h zjLp!1K+4B(8zh__If4NjIQuiwam@RMtE35<0?Y#X`yUA4zy?mS3czi-5dV&h?U*$Y zDA2~h0eITN$R73;-0c2q zL+F?d0|pXU1bF@zzn{b}I#g)@lMXNh>4=B<=N15UjJtS>1e5|48R^gcGyTKe0VC7H zfoM``%uj(p$4Llwvcnp7Hek>GC~4RwIQSF~Q0OE87+#`99P$t6&;O^7!ZD}LJb;EY ze~JSXD89r+&iN&Y-Y9USXa6q!1LGmGg%tyaG;NyIO97wQ^^eTuHaWwSbAPGgFO;Hh`!ybJ?+*mj` zMTMk7lBPZfnY-cR{?nPXnDNnqK(faiY@kR$fx}4;yvA~p!Nme-V;2(m90`29_(YAr z!QPtm019{H5RHR_@j8DGDO_X#3jP|ZBMtc&{xyUkn{S^qf^U#w@c&P;bf#pW#pNSl r{owN41gW5vq=Fug!`X}sBrQLOuZdjQ@yl>VI diff --git a/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts b/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts index 353349fd..ce62811c 100644 --- a/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts +++ b/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts @@ -4,10 +4,12 @@ import { PresentationContext as org_dcm4che3_net_pdu_PresentationContext } from import { Dimse as org_dcm4che3_net_Dimse } from "./../../../../dcm4che3/net/Dimse"; import { Attributes as org_dcm4che3_data_Attributes } from "./../../../../dcm4che3/data/Attributes"; import { PDVInputStream as org_dcm4che3_net_PDVInputStream } from "./../../../../dcm4che3/net/PDVInputStream"; +import { QueryRetrieveLevel2 as org_dcm4che3_net_service_QueryRetrieveLevel2 } from "./../../../../dcm4che3/net/service/QueryRetrieveLevel2"; import { Long as java_lang_Long } from "./../../../../../java/lang/Long"; import { Integer as java_lang_Integer } from "./../../../../../java/lang/Integer"; import { Class as java_lang_Class } from "./../../../../../java/lang/Class"; import { CFindSCPInject as org_github_chinlinlee_dcm777_net_CFindSCPInject, CFindSCPInjectInterface as org_github_chinlinlee_dcm777_net_CFindSCPInjectInterface } from "./CFindSCPInject"; +import { EnumSet as java_util_EnumSet } from "./../../../../../java/util/EnumSet"; /** * This class just defines types, you should import {@link BasicModCFindSCP} instead of this. * This was generated by java-bridge. @@ -52,14 +54,20 @@ export declare class BasicModCFindSCPClass extends JavaClass { onDimseRQSync(var0: org_dcm4che3_net_Association | null, var1: org_dcm4che3_net_pdu_PresentationContext | null, var2: org_dcm4che3_net_Dimse | null, var3: org_dcm4che3_data_Attributes | null, var4: org_dcm4che3_net_PDVInputStream | null): void; /** * @param var0 original type: 'org.dcm4che3.net.Association' - * @return original return type: 'void' + * @param var1 original type: 'org.dcm4che3.net.pdu.PresentationContext' + * @param var2 original type: 'org.dcm4che3.data.Attributes' + * @param var3 original type: 'org.dcm4che3.data.Attributes' + * @return original return type: 'org.dcm4che3.net.service.QueryRetrieveLevel2' */ - onClose(var0: org_dcm4che3_net_Association | null): Promise; + getQrLevel(var0: org_dcm4che3_net_Association | null, var1: org_dcm4che3_net_pdu_PresentationContext | null, var2: org_dcm4che3_data_Attributes | null, var3: org_dcm4che3_data_Attributes | null): Promise; /** * @param var0 original type: 'org.dcm4che3.net.Association' - * @return original return type: 'void' + * @param var1 original type: 'org.dcm4che3.net.pdu.PresentationContext' + * @param var2 original type: 'org.dcm4che3.data.Attributes' + * @param var3 original type: 'org.dcm4che3.data.Attributes' + * @return original return type: 'org.dcm4che3.net.service.QueryRetrieveLevel2' */ - onCloseSync(var0: org_dcm4che3_net_Association | null): void; + getQrLevelSync(var0: org_dcm4che3_net_Association | null, var1: org_dcm4che3_net_pdu_PresentationContext | null, var2: org_dcm4che3_data_Attributes | null, var3: org_dcm4che3_data_Attributes | null): org_dcm4che3_net_service_QueryRetrieveLevel2 | null; /** * @return original return type: 'java.lang.String[]' */ @@ -68,6 +76,16 @@ export declare class BasicModCFindSCPClass extends JavaClass { * @return original return type: 'java.lang.String[]' */ getSOPClassesSync(): (string | null)[] | null; + /** + * @param var0 original type: 'org.dcm4che3.net.Association' + * @return original return type: 'void' + */ + onClose(var0: org_dcm4che3_net_Association | null): Promise; + /** + * @param var0 original type: 'org.dcm4che3.net.Association' + * @return original return type: 'void' + */ + onCloseSync(var0: org_dcm4che3_net_Association | null): void; /** * @param var0 original type: 'long' * @param var1 original type: 'int' @@ -153,6 +171,13 @@ export declare class BasicModCFindSCPClass extends JavaClass { * @return original return type: 'org.github.chinlinlee.dcm777.net.BasicModCFindSCP' */ static newInstanceAsync(var0: (string | null)[] | null): Promise; + /** + * @param var0 original type: 'org.github.chinlinlee.dcm777.net.CFindSCPInject' + * @param var1 original type: 'java.lang.String' + * @param var2 original type: 'java.util.EnumSet' + * @return original return type: 'org.github.chinlinlee.dcm777.net.BasicModCFindSCP' + */ + static newInstanceAsync(var0: org_github_chinlinlee_dcm777_net_CFindSCPInject | JavaInterfaceProxy | null, var1: string | null, var2: java_util_EnumSet | null): Promise; /** * @param var0 original type: 'org.github.chinlinlee.dcm777.net.CFindSCPInject' * @param var1 original type: 'java.lang.String[]' @@ -163,6 +188,12 @@ export declare class BasicModCFindSCPClass extends JavaClass { * @param var0 original type: 'java.lang.String[]' */ constructor(var0: (string | null)[] | null); + /** + * @param var0 original type: 'org.github.chinlinlee.dcm777.net.CFindSCPInject' + * @param var1 original type: 'java.lang.String' + * @param var2 original type: 'java.util.EnumSet' + */ + constructor(var0: org_github_chinlinlee_dcm777_net_CFindSCPInject | JavaInterfaceProxy | null, var1: string | null, var2: java_util_EnumSet | null); /** * @param var0 original type: 'org.github.chinlinlee.dcm777.net.CFindSCPInject' * @param var1 original type: 'java.lang.String[]' diff --git a/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts.map b/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts.map index aa4477b1..4dacfa5d 100644 --- a/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts.map +++ b/models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"BasicModCFindSCP.d.ts","sourceRoot":"","sources":["../../../../../../src/java-wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,SAAS,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,WAAW,IAAI,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACrG,OAAO,EAAE,mBAAmB,IAAI,wCAAwC,EAAE,MAAM,oDAAoD,CAAC;AACrI,OAAO,EAAE,KAAK,IAAI,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,UAAU,IAAI,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACpG,OAAO,EAAE,cAAc,IAAI,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAC9G,OAAO,EAAE,IAAI,IAAI,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,EAAE,KAAK,IAAI,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,cAAc,IAAI,+CAA+C,EAAE,uBAAuB,IAAI,wDAAwD,EAAE,MAAM,kBAAkB,CAAC;AAE1L;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,SAAS;IAExD;;;;;;;OAOG;IACI,SAAS,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE5P;;;;;;;OAOG;IACI,aAAa,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI;IACvP;;;;;;;OAOG;IACI,SAAS,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,+BAA+B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/P;;;;;;;OAOG;IACI,aAAa,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,+BAA+B,GAAG,IAAI,GAAG,IAAI;IAE1P;;;OAGG;IACI,OAAO,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAExE;;;OAGG;IACI,WAAW,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI;IAEnE;;OAEG;IACI,aAAa,IAAI,OAAO,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IAEzD;;OAEG;IACI,iBAAiB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI;IAEpD;;;;OAIG;IACI,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEpG;;;;OAIG;IACI,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI;IAC/F;;OAEG;IACI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAC5B;;OAEG;IACI,QAAQ,IAAI,IAAI;IACvB;;;OAGG;IACI,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAClE;;;OAGG;IACI,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI;IAE7D;;;OAGG;IACI,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAE7D;;;OAGG;IACI,UAAU,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO;IAExD;;OAEG;IACI,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAElC;;OAEG;IACI,YAAY,IAAI,MAAM;IAE7B;;OAEG;IACI,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAElC;;OAEG;IACI,YAAY,IAAI,MAAM;IAE7B;;OAEG;IACI,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;IAE3C;;OAEG;IACI,YAAY,IAAI,eAAe;IAEtC;;OAEG;IACI,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAE9B;;OAEG;IACI,UAAU,IAAI,IAAI;IAEzB;;OAEG;IACI,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEjC;;OAEG;IACI,aAAa,IAAI,IAAI;IAE5B;;;OAGG;WACW,gBAAgB,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IACzF;;;;OAIG;WACW,gBAAgB,CAAC,IAAI,EAAE,+CAA+C,GAAG,kBAAkB,CAAC,wDAAwD,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAEtO;;OAEG;gBACgB,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI;IACjD;;;OAGG;gBACgB,IAAI,EAAE,+CAA+C,GAAG,kBAAkB,CAAC,wDAAwD,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI;CACjM;;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,SAAQ,qBAA8F;CACnI;AACD,eAAe,gBAAgB,CAAC"} \ No newline at end of file +{"version":3,"file":"BasicModCFindSCP.d.ts","sourceRoot":"","sources":["../../../../../../src/java-wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,SAAS,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,WAAW,IAAI,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACrG,OAAO,EAAE,mBAAmB,IAAI,wCAAwC,EAAE,MAAM,oDAAoD,CAAC;AACrI,OAAO,EAAE,KAAK,IAAI,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,UAAU,IAAI,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACpG,OAAO,EAAE,cAAc,IAAI,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAC9G,OAAO,EAAE,mBAAmB,IAAI,4CAA4C,EAAE,MAAM,wDAAwD,CAAC;AAC7I,OAAO,EAAE,IAAI,IAAI,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,EAAE,KAAK,IAAI,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,cAAc,IAAI,+CAA+C,EAAE,uBAAuB,IAAI,wDAAwD,EAAE,MAAM,kBAAkB,CAAC;AAC1L,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAElF;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,SAAS;IAExD;;;;;;;OAOG;IACI,SAAS,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE5P;;;;;;;OAOG;IACI,aAAa,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI;IACvP;;;;;;;OAOG;IACI,SAAS,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,+BAA+B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/P;;;;;;;OAOG;IACI,aAAa,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,sBAAsB,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,+BAA+B,GAAG,IAAI,GAAG,IAAI;IAE1P;;;;;;OAMG;IACI,UAAU,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,4CAA4C,GAAG,IAAI,CAAC;IAEvQ;;;;;;OAMG;IACI,cAAc,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,wCAAwC,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,EAAE,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,4CAA4C,GAAG,IAAI;IAElQ;;OAEG;IACI,aAAa,IAAI,OAAO,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IAEzD;;OAEG;IACI,iBAAiB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI;IAEpD;;;OAGG;IACI,OAAO,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAExE;;;OAGG;IACI,WAAW,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI;IAEnE;;;;OAIG;IACI,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEpG;;;;OAIG;IACI,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI;IAC/F;;OAEG;IACI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAC5B;;OAEG;IACI,QAAQ,IAAI,IAAI;IACvB;;;OAGG;IACI,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAClE;;;OAGG;IACI,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI;IAE7D;;;OAGG;IACI,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAE7D;;;OAGG;IACI,UAAU,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO;IAExD;;OAEG;IACI,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAElC;;OAEG;IACI,YAAY,IAAI,MAAM;IAE7B;;OAEG;IACI,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAElC;;OAEG;IACI,YAAY,IAAI,MAAM;IAE7B;;OAEG;IACI,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;IAE3C;;OAEG;IACI,YAAY,IAAI,eAAe;IAEtC;;OAEG;IACI,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAE9B;;OAEG;IACI,UAAU,IAAI,IAAI;IAEzB;;OAEG;IACI,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEjC;;OAEG;IACI,aAAa,IAAI,IAAI;IAE5B;;;OAGG;WACW,gBAAgB,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IACzF;;;;;OAKG;WACW,gBAAgB,CAAC,IAAI,EAAE,+CAA+C,GAAG,kBAAkB,CAAC,wDAAwD,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAC3P;;;;OAIG;WACW,gBAAgB,CAAC,IAAI,EAAE,+CAA+C,GAAG,kBAAkB,CAAC,wDAAwD,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAEtO;;OAEG;gBACgB,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI;IACjD;;;;OAIG;gBACgB,IAAI,EAAE,+CAA+C,GAAG,kBAAkB,CAAC,wDAAwD,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI;IACnN;;;OAGG;gBACgB,IAAI,EAAE,+CAA+C,GAAG,kBAAkB,CAAC,wDAAwD,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI;CACjM;;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,SAAQ,qBAA8F;CACnI;AACD,eAAe,gBAAgB,CAAC"} \ No newline at end of file From 4965b6260ef76c59c5fcefe1e8a24ac1f8498040 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 29 Dec 2023 14:15:15 +0800 Subject: [PATCH 275/365] feat: add Modality Worklist query support for C-FIND Introduce support for Modality Worklist (MWL) query in C-FIND to enable retrieval of worklist items. This change adds a new method to the JsCFindScp class, allowing for the retrieval of MWL data. This enhancement addresses the need for MWL query capability when performing DICOM C-FIND operations. --- dimse/c-find.js | 21 +++++ dimse/index.js | 1 + dimse/mwlQueryTask.js | 120 ++++++++++++++++++++++++ dimse/queryBuilder.js | 20 +++- dimse/queryTagsOfEachLevel.js | 47 ++++++++++ models/mongodb/models/mwlitems.model.js | 18 ++++ 6 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 dimse/mwlQueryTask.js diff --git a/dimse/c-find.js b/dimse/c-find.js index 1bd5c061..c0ab0304 100644 --- a/dimse/c-find.js +++ b/dimse/c-find.js @@ -12,6 +12,8 @@ const { JsStudyQueryTask } = require("@dimse-study-query-task"); const { JsSeriesQueryTask } = require("@dimse-series-query-task"); const { JsInstanceQueryTask } = require("@dimse-instance-query-task"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); +const { Tag } = require("@dcm4che/data/Tag"); +const { JsMwlQueryTask } = require("./mwlQueryTask"); class JsCFindScp { constructor() { } @@ -59,6 +61,20 @@ class JsCFindScp { return basicModCFindSCP; } + getMwlLevel() { + const cFindScpInject = createCFindSCPInjectProxy(this.getCFindScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + let basicModCFindSCP = new BasicModCFindSCP( + cFindScpInject, + [UID.ModalityWorklistInformationModelFind] + ); + + this.scpObj = basicModCFindSCP; + return basicModCFindSCP; + } + getCFindScpInjectProxyMethods() { /** * @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject").CFindSCPInjectInterface } @@ -83,6 +99,11 @@ class JsCFindScp { }, calculateMatches: async (as, pc, rq, keys) => { try { + let requestSopClassUID = await rq.getString(Tag.AffectedSOPClassUID); + if (requestSopClassUID === UID.ModalityWorklistInformationModelFind ) { + return await (new JsMwlQueryTask(as, pc, rq, keys)).get(); + } + let level = await this.scpObj.getQrLevel(as, pc, rq, keys); if (await level.compareTo(QueryRetrieveLevel2.PATIENT) === 0) { return await (new JsPatientQueryTask(as, pc, rq, keys)).get(); diff --git a/dimse/index.js b/dimse/index.js index fcf9ee3a..7086d08a 100644 --- a/dimse/index.js +++ b/dimse/index.js @@ -57,6 +57,7 @@ class DcmQrScp { await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientRootLevel()); await dicomServiceRegistry.addDicomService(new JsCFindScp().getStudyRootLevel()); await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientStudyOnlyLevel()); + await dicomServiceRegistry.addDicomService(new JsCFindScp().getMwlLevel()); // #endregion // #region C-MOVE diff --git a/dimse/mwlQueryTask.js b/dimse/mwlQueryTask.js new file mode 100644 index 00000000..c3708143 --- /dev/null +++ b/dimse/mwlQueryTask.js @@ -0,0 +1,120 @@ +const _ = require("lodash"); +const { PatientQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTask"); +const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); +const { Attributes } = require("@dcm4che/data/Attributes"); +const { Tag } = require("@dcm4che/data/Tag"); +const { VR } = require("@dcm4che/data/VR"); +const { Association } = require("@dcm4che/net/Association"); +const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); +const { logger } = require("@root/utils/logs/log"); +const { UID } = require("@dcm4che/data/UID"); +const { QueryTaskUtils } = require("./utils"); +const { default: BasicModQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModQueryTask"); +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); + + +class JsMwlQueryTask { + constructor(as, pc, rq, keys) { + /** @type { Association } */ + this.as = as; + /** @type { PresentationContext } */ + this.pc = pc; + /** @type { Attributes } */ + this.rq = rq; + /** @type { Attributes } */ + this.keys = keys; + + this.mwlAttr = null; + this.mwl = null; + } + + async get() { + let mwlQueryTask = await BasicModQueryTask.newInstanceAsync( + this.as, + this.pc, + this.rq, + this.keys, + this.getQueryTaskInjectProxy() + ); + + await this.initCursor(); + + return mwlQueryTask; + } + + getQueryTaskInjectProxy() { + // for creating one + if (!this.matchIteratorProxy) { + this.matchIteratorProxy = new MwlMatchIteratorProxy(this); + } + + return this.matchIteratorProxy.get(); + } + + /** + * + * @param {Attributes} match + * @returns + */ + async basicAdjust(match) { + if (match == null) { + return null; + } + + let filtered = new Attributes(await match.size()); + + await filtered.setNull(Tag.SpecificCharacterSet, VR.CS); + await filtered.addSelected(match, this.keys); + await filtered.supplementEmpty(this.keys); + return filtered; + } + + async initCursor() { + let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); + let dbQuery = await QueryTaskUtils.getDbQuery(this.keys, "mwl"); + queryAuditManager.onQuery( + UID.ModalityWorklistInformationModelFind, + JSON.stringify(dbQuery), + "UTF-8" + ); + + let returnKeys = await QueryTaskUtils.getReturnKeys(this.keys, "mwl"); + + logger.info(`do DIMSE Modality Work List query: ${JSON.stringify(dbQuery)}`); + this.cursor = await MwlItemModel.getDimseResultCursor({ + ...dbQuery + }, returnKeys); + } +} + +class MwlMatchIteratorProxy { + constructor(mwlQueryTask) { + /** @type {JsMwlQueryTask} */ + this.mwlQueryTask = mwlQueryTask; + } + + get() { + return createQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + hasMoreMatches: async () => { + this.mwlQueryTask.mwl = await this.mwlQueryTask.cursor.next(); + return !_.isNull(this.mwlQueryTask.mwl); + }, + nextMatch: async () => { + this.mwlQueryTask.mwlAttr = this.mwlQueryTask.mwl ? await this.mwlQueryTask.mwl.getAttributes() : null; + return this.mwlQueryTask.mwlAttr; + }, + adjust: async (match) => { + return this.mwlQueryTask.basicAdjust(match); + } + }; + } +} + +module.exports.JsMwlQueryTask = JsMwlQueryTask; +module.exports.MwlMatchIteratorProxy = MwlMatchIteratorProxy; \ No newline at end of file diff --git a/dimse/queryBuilder.js b/dimse/queryBuilder.js index b49c84ea..83a51663 100644 --- a/dimse/queryBuilder.js +++ b/dimse/queryBuilder.js @@ -5,15 +5,16 @@ const { queryTagsOfEachLevel } = require("./queryTagsOfEachLevel"); const { StringUtils } = require("@dcm4che/util/StringUtils"); const { intTagToString } = require("./utils"); const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); +const { default: Tag } = require("@dcm4che/data/Tag"); class DimseQueryBuilder { /** * * @param {Attributes} queryKeys - * @param {"patient" | "study" | "series" | "instance"} level + * @param {"patient" | "study" | "series" | "instance" | "mwl"} level */ - constructor(queryKeys, level="patient") { + constructor(queryKeys, level = "patient") { this.queryKeys = queryKeys; this.level = level; } @@ -21,7 +22,20 @@ class DimseQueryBuilder { async toNormalQuery() { const queryTags = queryTagsOfEachLevel[this.level]; let query = {}; - for (let i = 0 ; i < queryTags.length ; i++) { + + let spsKeys = await this.queryKeys.getNestedDataset(Tag.ScheduledProcedureStepSequence); + if (spsKeys != null && + !(await spsKeys.isEmpty()) && + this.level === "mwl" + ) { + let spsQueryTags = queryTagsOfEachLevel.mwlSps; + for (let i = 0; i < spsQueryTags.length; i++) { + let tagStringValues = await StringUtils.maskNull(await spsKeys.getStrings(spsQueryTags[i])); + query[`${intTagToString(Tag.ScheduledProcedureStepSequence)}.Value.${intTagToString(spsQueryTags[i])}.Value`] = tagStringValues.join(","); + } + } + + for (let i = 0; i < queryTags.length; i++) { let tag = queryTags[i]; /** @type {string[]} */ let tagStringValues = await StringUtils.maskNull(await this.queryKeys.getStrings(tag)); diff --git a/dimse/queryTagsOfEachLevel.js b/dimse/queryTagsOfEachLevel.js index aa50060d..aced5b8e 100644 --- a/dimse/queryTagsOfEachLevel.js +++ b/dimse/queryTagsOfEachLevel.js @@ -27,6 +27,53 @@ const queryTagsOfEachLevel = { Tag.SOPInstanceUID, Tag.SOPClassUID, Tag.InstanceNumber + ], + "mwl": [ + Tag.AccessionNumber, + Tag.ReferringPhysicianName, + Tag.ReferencedStudySequence, + Tag.ReferencedPatientSequence, + Tag.PatientName, + Tag.PatientID, + Tag.IssuerOfPatientID, + Tag.IssuerOfPatientIDQualifiersSequence, + Tag.PatientBirthDate, + Tag.PatientSex, + Tag.OtherPatientIDsSequence, + Tag.PatientWeight, + Tag.MedicalAlerts, + Tag.Allergies, + Tag.PregnancyStatus, + Tag.StudyInstanceUID, + Tag.RequestingPhysician, + Tag.RequestedProcedureDescription, + Tag.RequestedProcedureCodeSequence, + Tag.AdmissionID, + Tag.SpecialNeeds, + Tag.CurrentPatientLocation, + Tag.PatientState, + Tag.ScheduledProcedureStepSequence, + Tag.RequestedProcedureID, + Tag.RequestedProcedurePriority, + Tag.PatientTransportArrangements, + Tag.ConfidentialityConstraintOnPatientDataDescription, + Tag.WorklistLabel + ], + "mwlSps": [ + Tag.Modality, + Tag.AnatomicalOrientationType, + Tag.RequestedContrastAgent, + Tag.ScheduledStationAETitle, + Tag.ScheduledProcedureStepStartDate, + Tag.ScheduledProcedureStepStartTime, + Tag.ScheduledPerformingPhysicianName, + Tag.ScheduledProcedureStepDescription, + Tag.ScheduledProtocolCodeSequence, + Tag.ScheduledProcedureStepID, + Tag.ScheduledProcedureStepStatus, + Tag.ScheduledStationName, + Tag.ScheduledProcedureStepLocation, + Tag.PreMedication ] }; diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index ca1de43c..e7bf051b 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -5,6 +5,14 @@ const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { IncludeFieldsFactory } = require("../service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { raccoonConfig } = require("@root/config-class"); + +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + let mwlItemSchema = new mongoose.Schema( {}, @@ -15,6 +23,12 @@ let mwlItemSchema = new mongoose.Schema( getters: true }, statics: { + getDimseResultCursor: async function (query, keys) { + return mongoose.model("mwlItems").find(query, keys).setOptions({ + strictQuery: false + }) + .cursor(); + }, /** * * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions @@ -71,6 +85,10 @@ let mwlItemSchema = new mongoose.Schema( delete obj._id; delete obj.id; return obj; + }, + getAttributes: async function () { + let jsonStr = JSON.stringify(this.toDicomJson()); + return await Common.getAttributesFromJsonString(jsonStr); } } } From fefaee74e340f3cf9bb79cd9c8009661216b749a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 29 Dec 2023 15:13:22 +0800 Subject: [PATCH 276/365] refactor: use root scope module alias instead Refactor module aliases to use the root scope instead of absolute paths Utilize root scope module aliases instead of absolute paths for better clarity and maintainability of the codebase. This change makes it easier --- .gitignore | 4 +-- .../WADO-RS/service/thumbnail.service.js | 2 +- api/WADO-URI/controller/retrieveInstance.js | 2 +- .../MWL-RS/change-filtered-mwlItem-status.js | 2 +- .../MWL-RS/change-mwlItem-status.js | 2 +- .../controller/MWL-RS/count-mwlItem.js | 2 +- .../controller/MWL-RS/create-mwlItem.js | 2 +- .../controller/MWL-RS/get-mwlItem.js | 2 +- .../controller/QIDO-RS/base.controller.js | 2 +- .../QIDO-RS/service/QIDO-RS.service.js | 12 +++---- .../UPS-RS/change-workItem-state.js | 2 +- .../controller/UPS-RS/create-workItems.js | 2 +- .../controller/UPS-RS/get-workItem.js | 2 +- .../UPS-RS/service/cancel.service.js | 2 +- .../service/change-workItem-state.service.js | 2 +- .../UPS-RS/service/create-workItem.service.js | 4 +-- .../UPS-RS/service/subscribe.service.js | 2 +- .../service/suspend-subscription.service.js | 2 +- .../UPS-RS/service/unsubscribe.service.js | 2 +- .../UPS-RS/service/update-workItem.service.js | 2 +- api/dicom-web/controller/UPS-RS/subscribe.js | 2 +- .../controller/UPS-RS/suspend-subscription.js | 2 +- .../controller/UPS-RS/unsubscribe.js | 2 +- .../controller/UPS-RS/update-workItem.js | 2 +- .../controller/WADO-RS/base.controller.js | 2 +- .../WADO-RS/bulkdata/base.controller.js | 4 +-- .../controller/WADO-RS/bulkdata/bulkdata.js | 2 +- .../controller/WADO-RS/bulkdata/instance.js | 4 +-- .../controller/WADO-RS/bulkdata/series.js | 4 +-- .../controller/WADO-RS/bulkdata/study.js | 4 +-- .../WADO-RS/deletion/base.controller.js | 2 +- .../WADO-RS/metadata/base.controller.js | 2 +- .../metadata/retrieveInstanceMetadata.js | 2 +- .../metadata/retrieveSeriesMetadata.js | 2 +- .../WADO-RS/metadata/retrieveStudyMetadata.js | 2 +- .../WADO-RS/rendered/base.controller.js | 4 +-- .../WADO-RS/rendered/instanceFrames.js | 4 +-- .../controller/WADO-RS/rendered/instances.js | 4 +-- .../controller/WADO-RS/rendered/series.js | 4 +-- .../controller/WADO-RS/rendered/study.js | 4 +-- .../WADO-RS/service/thumbnail.service.js | 2 +- .../WADO-RS/thumbnail/base.controller.js | 2 +- config/jsconfig.mongodb.json | 36 ------------------- config/jsconfig.sql.json | 36 ------------------- config/modula-alias/mongodb/package.json | 15 -------- config/modula-alias/sql/package.json | 16 --------- dimse-sql/patientQueryTask.js | 2 +- dimse-sql/seriesQueryTask.js | 2 +- dimse/c-find.js | 8 ++--- dimse/c-get.js | 2 +- dimse/c-move.js | 2 +- dimse/utils.js | 2 +- jsconfig.json | 21 +++++++++++ 53 files changed, 87 insertions(+), 171 deletions(-) delete mode 100644 config/jsconfig.mongodb.json delete mode 100644 config/jsconfig.sql.json create mode 100644 jsconfig.json diff --git a/.gitignore b/.gitignore index 9d34e050..069815de 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,4 @@ config/ae-prod.properties # ignore jscpd output report -/report - -/jsconfig.json \ No newline at end of file +/report \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 1e635bc4..10b16063 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,4 +1,4 @@ -const renderedService = require("@rendered-service"); +const renderedService = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const _ = require("lodash"); const { ThumbnailService, diff --git a/api/WADO-URI/controller/retrieveInstance.js b/api/WADO-URI/controller/retrieveInstance.js index 4af01fba..78cc046c 100644 --- a/api/WADO-URI/controller/retrieveInstance.js +++ b/api/WADO-URI/controller/retrieveInstance.js @@ -1,4 +1,4 @@ -const { WadoUriService, NotFoundInstanceError } = require("@wado-uri-service"); +const { WadoUriService } = require("@api/WADO-URI/service/WADO-URI.service"); const { Controller } = require("../../controller.class"); const { ApiLogger } = require("../../../utils/logs/api-logger"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); diff --git a/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js index 9836a99a..65cd9eda 100644 --- a/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/change-filtered-mwlItem-status.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { ChangeFilteredMwlItemStatusService } = require("@mwl-service/change-filtered-mwlItem-status"); +const { ChangeFilteredMwlItemStatusService } = require("@api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status"); class ChangeFilteredMwlItemStatusController extends Controller { constructor(req, res) { diff --git a/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js index 5b7a04aa..f0873724 100644 --- a/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { ChangeMwlItemStatusService } = require("@mwl-service/change-mwlItem-status"); +const { ChangeMwlItemStatusService } = require("@api/dicom-web/controller/MWL-RS/service/change-mwlItem-status"); class ChangeMwlItemStatusController extends Controller { constructor(req, res) { diff --git a/api/dicom-web/controller/MWL-RS/count-mwlItem.js b/api/dicom-web/controller/MWL-RS/count-mwlItem.js index f64debfa..ab630370 100644 --- a/api/dicom-web/controller/MWL-RS/count-mwlItem.js +++ b/api/dicom-web/controller/MWL-RS/count-mwlItem.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { GetMwlItemCountService } = require("@mwl-service/count-mwlItem.service"); +const { GetMwlItemCountService } = require("@api/dicom-web/controller/MWL-RS/service/count-mwlItem.service"); class GetMwlItemCountController extends Controller { constructor(req, res) { diff --git a/api/dicom-web/controller/MWL-RS/create-mwlItem.js b/api/dicom-web/controller/MWL-RS/create-mwlItem.js index aa387248..152b3966 100644 --- a/api/dicom-web/controller/MWL-RS/create-mwlItem.js +++ b/api/dicom-web/controller/MWL-RS/create-mwlItem.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { CreateMwlItemService } = require("@mwl-service/create-mwlitem.service"); +const { CreateMwlItemService } = require("@api/dicom-web/controller/MWL-RS/service/create-mwlitem.service"); class CreateMwlItemController extends Controller { constructor(req, res) { diff --git a/api/dicom-web/controller/MWL-RS/get-mwlItem.js b/api/dicom-web/controller/MWL-RS/get-mwlItem.js index b3065fec..4e78a132 100644 --- a/api/dicom-web/controller/MWL-RS/get-mwlItem.js +++ b/api/dicom-web/controller/MWL-RS/get-mwlItem.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { GetMwlItemService } = require("@mwl-service/get-mwlItem.service"); +const { GetMwlItemService } = require("@api/dicom-web/controller/MWL-RS/service/get-mwlItem.service"); class GetMwlItemController extends Controller { constructor(req, res) { diff --git a/api/dicom-web/controller/QIDO-RS/base.controller.js b/api/dicom-web/controller/QIDO-RS/base.controller.js index ce69d448..f26448ba 100644 --- a/api/dicom-web/controller/QIDO-RS/base.controller.js +++ b/api/dicom-web/controller/QIDO-RS/base.controller.js @@ -1,6 +1,6 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { QidoRsService } = require("@qido-rs-service"); +const { QidoRsService } = require("@api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseQueryController extends Controller { diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 2d269133..ca6d14da 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -1,14 +1,14 @@ const _ = require("lodash"); -const { dictionary } = require("../../../../../models/DICOM/dicom-tags-dic"); const { DicomWebService } = require("../../../service/dicom-web.service"); -const { - DicomWebServiceError, - DicomWebStatusCodes -} = require("@error/dicom-web-service"); const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); -const { QueryPatientDicomJsonFactory, QueryStudyDicomJsonFactory, QuerySeriesDicomJsonFactory, QueryInstanceDicomJsonFactory } = require("@query-dicom-json-factory"); +const { + QueryPatientDicomJsonFactory, + QueryStudyDicomJsonFactory, + QuerySeriesDicomJsonFactory, + QueryInstanceDicomJsonFactory +} = require("@api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); const HierarchyQueryDicomJsonFactory = Object.freeze({ diff --git a/api/dicom-web/controller/UPS-RS/change-workItem-state.js b/api/dicom-web/controller/UPS-RS/change-workItem-state.js index ce353d7f..ab2a3faa 100644 --- a/api/dicom-web/controller/UPS-RS/change-workItem-state.js +++ b/api/dicom-web/controller/UPS-RS/change-workItem-state.js @@ -1,6 +1,6 @@ const { ChangeWorkItemStateService -} = require("@ups-service/change-workItem-state.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/change-workItem-state.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/api/dicom-web/controller/UPS-RS/create-workItems.js b/api/dicom-web/controller/UPS-RS/create-workItems.js index 78269bcd..3cbf429b 100644 --- a/api/dicom-web/controller/UPS-RS/create-workItems.js +++ b/api/dicom-web/controller/UPS-RS/create-workItems.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { CreateWorkItemService -} = require("@ups-service/create-workItem.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/create-workItem.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/api/dicom-web/controller/UPS-RS/get-workItem.js b/api/dicom-web/controller/UPS-RS/get-workItem.js index 133e0c6f..bf87c0ee 100644 --- a/api/dicom-web/controller/UPS-RS/get-workItem.js +++ b/api/dicom-web/controller/UPS-RS/get-workItem.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { GetWorkItemService -} = require("@ups-service/get-workItem.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/get-workItem.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index d5384075..42a82729 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -4,7 +4,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); +const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { raccoonConfig } = require("@root/config-class"); diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index f3536065..3ded0248 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -7,7 +7,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); +const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); class ChangeWorkItemStateService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 9553539c..0a8a18f7 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -7,8 +7,8 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { DicomJsonModel } = require("@dicom-json-model"); -const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); -const { SubscribeService } = require("@ups-service/subscribe.service"); +const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); +const { SubscribeService } = require("@api/dicom-web/controller/UPS-RS/service/subscribe.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index 41195066..d0d7f0eb 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -9,7 +9,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); -const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); +const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); diff --git a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js index e2bf8e60..f47e5c0c 100644 --- a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js +++ b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js @@ -4,7 +4,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); +const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); class SuspendSubscribeService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index c48ca2c2..655ab405 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -9,7 +9,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); -const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); +const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); class UnSubscribeService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index e028d3b2..2b3f1c6f 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -7,7 +7,7 @@ const { DicomWebStatusCodes } = require("@error/dicom-web-service"); const { DicomJsonModel } = require("@dicom-json-model"); -const { BaseWorkItemService } = require("@ups-service/base-workItem.service"); +const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); diff --git a/api/dicom-web/controller/UPS-RS/subscribe.js b/api/dicom-web/controller/UPS-RS/subscribe.js index eaef8621..f1a11e13 100644 --- a/api/dicom-web/controller/UPS-RS/subscribe.js +++ b/api/dicom-web/controller/UPS-RS/subscribe.js @@ -1,6 +1,6 @@ const { SubscribeService -} = require("@ups-service/subscribe.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/subscribe.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/api/dicom-web/controller/UPS-RS/suspend-subscription.js b/api/dicom-web/controller/UPS-RS/suspend-subscription.js index a3128cdf..4e0dc215 100644 --- a/api/dicom-web/controller/UPS-RS/suspend-subscription.js +++ b/api/dicom-web/controller/UPS-RS/suspend-subscription.js @@ -6,7 +6,7 @@ const { SuspendSubscribeService -} = require("@ups-service/suspend-subscription.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/suspend-subscription.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/api/dicom-web/controller/UPS-RS/unsubscribe.js b/api/dicom-web/controller/UPS-RS/unsubscribe.js index f6316372..5d455821 100644 --- a/api/dicom-web/controller/UPS-RS/unsubscribe.js +++ b/api/dicom-web/controller/UPS-RS/unsubscribe.js @@ -1,6 +1,6 @@ const { UnSubscribeService -} = require("@ups-service/unsubscribe.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/unsubscribe.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/api/dicom-web/controller/UPS-RS/update-workItem.js b/api/dicom-web/controller/UPS-RS/update-workItem.js index 267ccfbb..ac5d955a 100644 --- a/api/dicom-web/controller/UPS-RS/update-workItem.js +++ b/api/dicom-web/controller/UPS-RS/update-workItem.js @@ -1,6 +1,6 @@ const { UpdateWorkItemService -} = require("@ups-service/update-workItem.service"); +} = require("@api/dicom-web/controller/UPS-RS/service/update-workItem.service"); const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { DicomWebServiceError } = require("@error/dicom-web-service"); diff --git a/api/dicom-web/controller/WADO-RS/base.controller.js b/api/dicom-web/controller/WADO-RS/base.controller.js index 0cb72f13..bc4c30be 100644 --- a/api/dicom-web/controller/WADO-RS/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/base.controller.js @@ -11,7 +11,7 @@ const { InstanceImagePathFactory, multipartContentTypeWriter, StudyImagePathFactory, - SeriesImagePathFactory } = require("@wado-rs-service"); + SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseRetrieveController extends Controller { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js index 3d1e56d1..8403f66d 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js @@ -1,7 +1,7 @@ const { Controller } = require("@root/api/controller.class"); -const { StudyBulkDataFactory, BulkDataService } = require("@bulkdata-service"); +const { StudyBulkDataFactory, BulkDataService } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { StudyImagePathFactory } = require("@wado-rs-service"); +const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseBulkDataController extends Controller { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js index 7da20b55..85da4720 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js @@ -1,4 +1,4 @@ -const { SpecificBulkDataFactory } = require("@bulkdata-service"); +const { SpecificBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); const { BaseBulkDataController } = require("./base.controller"); class BulkDataController extends BaseBulkDataController { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js index eaa5e16a..44b76c81 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js @@ -1,6 +1,6 @@ -const { InstanceBulkDataFactory } = require("@bulkdata-service"); +const { InstanceBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); const { BaseBulkDataController } = require("./base.controller"); -const { InstanceImagePathFactory } = require("@wado-rs-service"); +const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class InstanceBulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/series.js b/api/dicom-web/controller/WADO-RS/bulkdata/series.js index e66164be..e1a253a7 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/series.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/series.js @@ -1,6 +1,6 @@ -const { SeriesBulkDataFactory } = require("@bulkdata-service"); +const { SeriesBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); const { BaseBulkDataController } = require("./base.controller"); -const { SeriesImagePathFactory } = require("@wado-rs-service"); +const { SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class SeriesBulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/study.js b/api/dicom-web/controller/WADO-RS/bulkdata/study.js index 01f00204..82c5b15d 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/study.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/study.js @@ -1,6 +1,6 @@ -const { StudyBulkDataFactory } = require("@bulkdata-service"); +const { StudyBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); const { BaseBulkDataController } = require("./base.controller"); -const { StudyImagePathFactory } = require("@wado-rs-service"); +const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class StudyBulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js index 6243a569..e6f96059 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/deletion/base.controller.js @@ -1,6 +1,6 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { DeleteService } = require("@delete-service"); +const { DeleteService } = require("@api/dicom-web/controller/WADO-RS/deletion/service/delete"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseDeleteController extends Controller { diff --git a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js index c5cf7f9f..5a53c00e 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/metadata/base.controller.js @@ -1,6 +1,6 @@ const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { StudyImagePathFactory } = require("@wado-rs-service"); +const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const { MetadataService } = require("../service/metadata.service"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js index 61f0b21e..23c94544 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js @@ -1,5 +1,5 @@ const { BaseRetrieveMetadataController } = require("./base.controller"); -const { InstanceImagePathFactory } = require("@wado-rs-service"); +const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class RetrieveInstanceMetadataController extends BaseRetrieveMetadataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js index b44a26c5..d04393ad 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js @@ -1,5 +1,5 @@ const { BaseRetrieveMetadataController } = require("./base.controller"); -const { SeriesImagePathFactory } = require("@wado-rs-service"); +const { SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class RetrieveSeriesMetadataController extends BaseRetrieveMetadataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js index d8658ec9..416f5c91 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js @@ -1,4 +1,4 @@ -const { StudyImagePathFactory } = require("@wado-rs-service"); +const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const { BaseRetrieveMetadataController } = require("./base.controller"); class RetrieveStudyMetadataController extends BaseRetrieveMetadataController { diff --git a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js index 71f9e6af..34d26913 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/rendered/base.controller.js @@ -1,8 +1,8 @@ const _ = require("lodash"); -const renderedService = require("@rendered-service"); +const renderedService = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const { StudyImagePathFactory -} = require("@wado-rs-service"); +} = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); const { ApiLogger } = require("@root/utils/logs/api-logger"); const { Controller } = require("../../../../controller.class"); diff --git a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js b/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js index eee25b09..07a3fa4d 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js +++ b/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js @@ -1,7 +1,7 @@ const _ = require("lodash"); -const { InstanceImagePathFactory } = require("@wado-rs-service"); +const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const { BaseRetrieveRenderedController } = require("./base.controller"); -const { InstanceFramesListWriter } = require("@rendered-service"); +const { InstanceFramesListWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); class RetrieveRenderedInstanceFramesController extends BaseRetrieveRenderedController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/rendered/instances.js b/api/dicom-web/controller/WADO-RS/rendered/instances.js index 46324f05..c9c48250 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/instances.js +++ b/api/dicom-web/controller/WADO-RS/rendered/instances.js @@ -1,5 +1,5 @@ -const { InstanceImagePathFactory } = require("@wado-rs-service"); -const { InstanceFramesWriter } = require("@rendered-service"); +const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); +const { InstanceFramesWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const { BaseRetrieveRenderedController } = require("./base.controller"); class RetrieveRenderedInstancesController extends BaseRetrieveRenderedController { diff --git a/api/dicom-web/controller/WADO-RS/rendered/series.js b/api/dicom-web/controller/WADO-RS/rendered/series.js index 92a1e1d2..02620c13 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/series.js +++ b/api/dicom-web/controller/WADO-RS/rendered/series.js @@ -1,5 +1,5 @@ -const { SeriesImagePathFactory } = require("@wado-rs-service"); -const { SeriesFramesWriter } = require("@rendered-service"); +const { SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); +const { SeriesFramesWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const { BaseRetrieveRenderedController } = require("./base.controller"); class RetrieveRenderedSeriesController extends BaseRetrieveRenderedController { diff --git a/api/dicom-web/controller/WADO-RS/rendered/study.js b/api/dicom-web/controller/WADO-RS/rendered/study.js index 84bc6f36..f5fffafa 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/study.js +++ b/api/dicom-web/controller/WADO-RS/rendered/study.js @@ -1,5 +1,5 @@ -const { StudyImagePathFactory } = require("@wado-rs-service"); -const { StudyFramesWriter } = require("@rendered-service"); +const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); +const { StudyFramesWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const { BaseRetrieveRenderedController } = require("./base.controller"); class RetrieveRenderedStudyController extends BaseRetrieveRenderedController { diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 82937497..539f877a 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -1,6 +1,6 @@ const { InstanceModel } = require("@dbModels/instance.model"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); -const renderedService = require("@rendered-service"); +const renderedService = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const _ = require("lodash"); const { getUidsString } = require("./WADO-RS.service"); class ThumbnailService { diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js index e09c1fd3..c657a3ba 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js @@ -1,5 +1,5 @@ const { Controller } = require("@root/api/controller.class"); -const { StudyImagePathFactory } = require("@wado-rs-service"); +const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const { ThumbnailService } = require("../service/thumbnail.service"); const { ApiLogger } = require("@root/utils/logs/api-logger"); diff --git a/config/jsconfig.mongodb.json b/config/jsconfig.mongodb.json deleted file mode 100644 index 199b4dd8..00000000 --- a/config/jsconfig.mongodb.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "compilerOptions": { - "module": "CommonJS", - "paths": { - "@dcm4che/*": ["./models/DICOM/dcm4che/wrapper/org/dcm4che3/*"], - "@java-wrapper/*": ["./models/DICOM/dcm4che/wrapper/*"], - "@models/*": ["./models/*"], - "@error/*" : ["./error/*"], - "@root/*": ["./*"], - "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], - "@dbModels/*": ["./models/mongodb/models/*"], - "@dicom-json-model": ["./models/DICOM/dicom-json-model.js"], - "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"], - "@stow-rs-service": ["./api/dicom-web/controller/STOW-RS/service/stow-rs.service.js"], - "@qido-rs-service": ["./api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], - "@wado-rs-service": ["./api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], - "@wado-uri-service": ["./api/WADO-URI/service/WADO-URI.service.js"], - "@bulkdata-service": ["./api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], - "@delete-service": ["./api/dicom-web/controller/WADO-RS/deletion/service/delete.js"], - "@rendered-service": ["./api/dicom-web/controller/WADO-RS/service/rendered.service.js"], - "@thumbnail-service": ["./api/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], - "@ups-service/*": ["./api/dicom-web/controller/UPS-RS/service/*"], - "@mwl-service/*": ["./api/dicom-web/controller/MWL-RS/service/*"], - "@dimse-query-builder": ["./dimse/queryBuilder.js"], - "@dimse-patient-query-task": ["./dimse/patientQueryTask.js"], - "@dimse-study-query-task": ["./dimse/studyQueryTask.js"], - "@dimse-series-query-task": ["./dimse/seriesQueryTask.js"], - "@dimse-instance-query-task": ["./dimse/instanceQueryTask.js"], - "@dimse-utils": ["./dimse/utils.js"], - "@api/*": ["./api/*"] - } - }, - "exclude": [ - "node_modules", "**/node_modules/*" - ] -} \ No newline at end of file diff --git a/config/jsconfig.sql.json b/config/jsconfig.sql.json deleted file mode 100644 index c4d1a146..00000000 --- a/config/jsconfig.sql.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "compilerOptions": { - "module": "CommonJS", - "paths": { - "@dcm4che/*": ["./models/DICOM/dcm4che/wrapper/org/dcm4che3/*"], - "@java-wrapper/*": ["./models/DICOM/dcm4che/wrapper/*"], - "@models/*": ["./models/*"], - "@error/*" : ["./error/*"], - "@root/*": ["./*"], - "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], - "@dbModels/*": ["./models/sql/models/*"], - "@dicom-json-model": ["./models/sql/dicom-json-model.js"], - "@query-dicom-json-factory": ["./api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js"], - "@stow-rs-service": ["./api/dicom-web/controller/STOW-RS/service/stow-rs.service.js"], - "@qido-rs-service": ["./api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js"], - "@wado-rs-service": ["./api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js"], - "@wado-uri-service": ["./api-sql/WADO-URI/service/WADO-URI.service.js"], - "@bulkdata-service": ["./api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js"], - "@delete-service": ["./api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js"], - "@rendered-service": ["./api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js"], - "@thumbnail-service": ["./api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js"], - "@ups-service/*": ["./api-sql/dicom-web/controller/UPS-RS/service/*"], - "@mwl-service/*": ["./api-sql/dicom-web/controller/MWL-RS/service/*"], - "@dimse-query-builder": ["./dimse-sql/queryBuilder.js"], - "@dimse-patient-query-task": ["./dimse-sql/patientQueryTask.js"], - "@dimse-study-query-task": ["./dimse-sql/studyQueryTask.js"], - "@dimse-series-query-task": ["./dimse-sql/seriesQueryTask.js"], - "@dimse-instance-query-task": ["./dimse-sql/instanceQueryTask.js"], - "@dimse-utils": ["./dimse-sql/utils.js"], - "@api/*": ["./api-sql/*"] - } - }, - "exclude": [ - "node_modules", "**/node_modules/*" - ] -} \ No newline at end of file diff --git a/config/modula-alias/mongodb/package.json b/config/modula-alias/mongodb/package.json index 1ccf6c60..db14a9b7 100644 --- a/config/modula-alias/mongodb/package.json +++ b/config/modula-alias/mongodb/package.json @@ -11,21 +11,6 @@ "@dicom-json-model": "../../../models/DICOM/dicom-json-model.js", "@query-dicom-json-factory": "../../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js", "@stow-rs-service": "../../../api/dicom-web/controller/STOW-RS/service/stow-rs.service.js", - "@qido-rs-service": "../../../api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", - "@wado-rs-service": "../../../api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", - "@wado-uri-service": "../../../api/WADO-URI/service/WADO-URI.service.js", - "@bulkdata-service": "../../../api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", - "@delete-service": "../../../api/dicom-web/controller/WADO-RS/deletion/service/delete.js", - "@rendered-service": "../../../api/dicom-web/controller/WADO-RS/service/rendered.service.js", - "@thumbnail-service": "../../../api/dicom-web/controller/WADO-RS/service/thumbnail.service.js", - "@ups-service": "../../../api/dicom-web/controller/UPS-RS/service", - "@mwl-service": "../../../api/dicom-web/controller/MWL-RS/service", - "@dimse-query-builder": "../../../dimse/queryBuilder.js", - "@dimse-patient-query-task": "../../../dimse/patientQueryTask.js", - "@dimse-study-query-task": "../../../dimse/studyQueryTask.js", - "@dimse-series-query-task": "../../../dimse/seriesQueryTask.js", - "@dimse-instance-query-task": "../../../dimse/instanceQueryTask.js", - "@dimse-utils": "../../../dimse/utils.js", "@dimse": "../../../dimse", "@api": "../../../api" } diff --git a/config/modula-alias/sql/package.json b/config/modula-alias/sql/package.json index cf5845c7..01621c48 100644 --- a/config/modula-alias/sql/package.json +++ b/config/modula-alias/sql/package.json @@ -9,23 +9,7 @@ "@dbModels": "../../../models/sql/models", "@dbInitializer": "../../../models/sql/initializer.js", "@dicom-json-model": "../../../models/sql/dicom-json-model.js", - "@query-dicom-json-factory": "../../../api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js", "@stow-rs-service": "../../../api/dicom-web/controller/STOW-RS/service/stow-rs.service.js", - "@qido-rs-service": "../../../api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js", - "@wado-rs-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js", - "@wado-uri-service": "../../../api-sql/WADO-URI/service/WADO-URI.service.js", - "@bulkdata-service": "../../../api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js", - "@delete-service": "../../../api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js", - "@rendered-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js", - "@thumbnail-service": "../../../api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js", - "@ups-service": "../../../api-sql/dicom-web/controller/UPS-RS/service", - "@mwl-service": "../../../api-sql/dicom-web/controller/MWL-RS/service", - "@dimse-query-builder": "../../../dimse-sql/queryBuilder.js", - "@dimse-patient-query-task": "../../../dimse-sql/patientQueryTask.js", - "@dimse-study-query-task": "../../../dimse-sql/studyQueryTask.js", - "@dimse-series-query-task": "../../../dimse-sql/seriesQueryTask.js", - "@dimse-instance-query-task": "../../../dimse-sql/instanceQueryTask.js", - "@dimse-utils": "../../../dimse-sql/utils.js", "@dimse": "../../../dimse-sql", "@api": "../../../api-sql" } diff --git a/dimse-sql/patientQueryTask.js b/dimse-sql/patientQueryTask.js index 42e4145b..4c0b2fe6 100644 --- a/dimse-sql/patientQueryTask.js +++ b/dimse-sql/patientQueryTask.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const { createPatientQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTaskInject"); -const { DimseQueryBuilder } = require("@dimse-query-builder"); +const { DimseQueryBuilder } = require("@dimse/queryBuilder"); const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); const { PatientModel } = require("@models/sql/models/patient.model"); const { JsPatientQueryTask } = require("../dimse/patientQueryTask"); diff --git a/dimse-sql/seriesQueryTask.js b/dimse-sql/seriesQueryTask.js index bcdcf38d..7cc46e5c 100644 --- a/dimse-sql/seriesQueryTask.js +++ b/dimse-sql/seriesQueryTask.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); -const { DimseQueryBuilder } = require("@dimse-query-builder"); +const { DimseQueryBuilder } = require("@dimse/queryBuilder"); const { JsStudyQueryTask } = require("./studyQueryTask"); const { SeriesQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTask"); const { Attributes } = require("@dcm4che/data/Attributes"); diff --git a/dimse/c-find.js b/dimse/c-find.js index c0ab0304..824b3a27 100644 --- a/dimse/c-find.js +++ b/dimse/c-find.js @@ -7,10 +7,10 @@ const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); const { QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetrieveLevel2"); const { BasicModCFindSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP"); const { createCFindSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject"); -const { JsPatientQueryTask } = require("@dimse-patient-query-task"); -const { JsStudyQueryTask } = require("@dimse-study-query-task"); -const { JsSeriesQueryTask } = require("@dimse-series-query-task"); -const { JsInstanceQueryTask } = require("@dimse-instance-query-task"); +const { JsPatientQueryTask } = require("@dimse/patientQueryTask"); +const { JsStudyQueryTask } = require("@dimse/studyQueryTask"); +const { JsSeriesQueryTask } = require("@dimse/seriesQueryTask"); +const { JsInstanceQueryTask } = require("@dimse/instanceQueryTask"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); const { Tag } = require("@dcm4che/data/Tag"); const { JsMwlQueryTask } = require("./mwlQueryTask"); diff --git a/dimse/c-get.js b/dimse/c-get.js index 3b1ff5bc..54e54dad 100644 --- a/dimse/c-get.js +++ b/dimse/c-get.js @@ -2,7 +2,7 @@ const { UID } = require("@dcm4che/data/UID"); const { createCGetSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CGetSCPInject"); const { SimpleCGetSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCGetSCP"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); -const { getInstancesFromKeysAttr } = require("@dimse-utils"); +const { getInstancesFromKeysAttr } = require("@dimse/utils"); const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); const { Dimse } = require("@dcm4che/net/Dimse"); diff --git a/dimse/c-move.js b/dimse/c-move.js index e56215b2..684b552b 100644 --- a/dimse/c-move.js +++ b/dimse/c-move.js @@ -13,7 +13,7 @@ const { AAssociateRQ } = require("@dcm4che/net/pdu/AAssociateRQ"); const { Connection } = require("@dcm4che/net/Connection"); const { RetrieveTaskImpl } = require("@chinlinlee/dcm777/dcmqrscp/RetrieveTaskImpl"); const { Dimse } = require("@dcm4che/net/Dimse"); -const { getInstancesFromKeysAttr } = require("@dimse-utils"); +const { getInstancesFromKeysAttr } = require("@dimse/utils"); const { createRetrieveAuditInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/dcmqrscp/RetrieveAuditInject"); const { DimseRetrieveAuditService } = require("./service/retrieveAudit.service"); diff --git a/dimse/utils.js b/dimse/utils.js index 595af81d..beca3753 100644 --- a/dimse/utils.js +++ b/dimse/utils.js @@ -113,7 +113,7 @@ class QueryTaskUtils { } static async getQueryBuilder(queryAttr, level = "patient") { - const { DimseQueryBuilder } = require("@dimse-query-builder"); + const { DimseQueryBuilder } = require("@dimse/queryBuilder"); return new DimseQueryBuilder(queryAttr, level); } diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 00000000..13195588 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "paths": { + "@dcm4che/*": ["./models/DICOM/dcm4che/wrapper/org/dcm4che3/*"], + "@java-wrapper/*": ["./models/DICOM/dcm4che/wrapper/*"], + "@models/*": ["./models/*"], + "@error/*" : ["./error/*"], + "@root/*": ["./*"], + "@chinlinlee/*": ["./models/DICOM/dcm4che/wrapper/org/github/chinlinlee/*"], + "@dbModels/*": ["./models/mongodb/models/*"], + "@dicom-json-model": ["./models/mongodb/dicom-json-model.js"], + "@stow-rs-service": ["./api/dicom-web/controller/STOW-RS/service/stow-rs.service.js"], + "@dimse/*": ["./dimse/*"], + "@api/*": ["./api/*"] + } + }, + "exclude": [ + "node_modules", "**/node_modules/*" + ] +} \ No newline at end of file From 7267a0409ca3bad1ec6d3f36900bd922f1f6cb4c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 1 Jan 2024 20:22:14 +0800 Subject: [PATCH 277/365] refactor: split `getAndResponseDicomJson` to `getDicomJson` and move response process to controller --- .../controller/QIDO-RS/base.controller.js | 6 +++++- .../QIDO-RS/service/QIDO-RS.service.js | 16 +++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/api/dicom-web/controller/QIDO-RS/base.controller.js b/api/dicom-web/controller/QIDO-RS/base.controller.js index f26448ba..7a1dfb3b 100644 --- a/api/dicom-web/controller/QIDO-RS/base.controller.js +++ b/api/dicom-web/controller/QIDO-RS/base.controller.js @@ -20,7 +20,11 @@ class BaseQueryController extends Controller { try { let qidoRsService = new QidoRsService(this.request, this.response, this.level); - await qidoRsService.getAndResponseDicomJson(); + let foundDicomJson = await qidoRsService.getDicomJson(); + if (foundDicomJson.length === 0 ) { + return this.response.status(204).send(); + } + return this.response.status(200).set("Content-Type", "application/dicom+json").json(foundDicomJson); } catch (e) { let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); return apiErrorArrayHandler.doErrorResponse(); diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index ca6d14da..78188d00 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -73,7 +73,8 @@ class QidoRsService { this.query = convertAllQueryToDicomTag(query); } - async getAndResponseDicomJson() { + + async getDicomJson() { try { let queryAudit = new AuditManager( EventType.QUERY, @@ -82,7 +83,6 @@ class QidoRsService { DicomWebService.getServerAddress(), DicomWebService.getServerHostname() ); let dicomWebService = new DicomWebService(this.request, this.response); - let queryOptions = { query: this.query, skip: this.skip_, @@ -104,15 +104,9 @@ class QidoRsService { let dicomJsonLength = _.get(dicomJson, "length", 0); if (dicomJsonLength > 0) { this.auditInstancesAccessed(dicomJson); - this.response.writeHead(200, { - "Content-Type": "application/dicom+json" - }); - this.response.end(JSON.stringify(dicomJson)); - } else { - this.response.writeHead(204); - this.response.end(); - } - + return dicomJson; + } + return []; } catch(e) { throw e; } From a91ad1189fc35d25d3354b284598b0e5827a3dfb Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 1 Jan 2024 20:29:10 +0800 Subject: [PATCH 278/365] refactor: split `getAndResponseDicomInstance` into two responsibility things - remove `getAndResponseDicomInstance` method in wado uri service - for right responsibility: - response should in controller, not in service - so, move `setHeader` and stream.pipe into controller --- api/WADO-URI/controller/retrieveInstance.js | 19 +++++++++++-------- api/WADO-URI/service/WADO-URI.service.js | 17 ----------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/api/WADO-URI/controller/retrieveInstance.js b/api/WADO-URI/controller/retrieveInstance.js index 78cc046c..7dafb77c 100644 --- a/api/WADO-URI/controller/retrieveInstance.js +++ b/api/WADO-URI/controller/retrieveInstance.js @@ -11,7 +11,7 @@ class RetrieveSingleInstanceController extends Controller { } async mainProcess() { - let { + let { contentType } = this.request.query; @@ -19,19 +19,22 @@ class RetrieveSingleInstanceController extends Controller { try { - if (contentType === "application/dicom") { - this.service.getAndResponseDicomInstance(); + if (contentType === "application/dicom" || !contentType) { + + let dicomInstanceReadStream = await this.service.getDicomInstanceReadStream(); + this.response.setHeader("Content-Type", "application/dicom"); + dicomInstanceReadStream.pipe(this.response); } else if (contentType === "image/jpeg") { this.service.getAndResponseJpeg(); - } else if (!contentType) { - this.service.getAndResponseDicomInstance(); } - } catch(e) { + this.response.on("finish", () => this.service.auditInstanceTransferred()); + + } catch (e) { let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.logger, e); return apiErrorArrayHandler.doErrorResponse(); } - + } } @@ -41,7 +44,7 @@ class RetrieveSingleInstanceController extends Controller { * @param {import("http").IncomingMessage} req * @param {import("http").ServerResponse} res */ -module.exports = async function(req, res) { +module.exports = async function (req, res) { let controller = new RetrieveSingleInstanceController(req, res); await controller.doPipeline(); diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 36365fa5..8052bdef 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -28,23 +28,6 @@ class WadoUriService { this.auditBeginTransferring(); } - async getAndResponseDicomInstance() { - try { - - let dicomInstanceReadStream = await this.getDicomInstanceReadStream(); - - this.response.setHeader("Content-Type", "application/dicom"); - dicomInstanceReadStream.pipe(this.response); - this.auditInstanceTransferred(); - - } catch (e) { - this.auditInstanceTransferred(EventOutcomeIndicator.MajorFailure); - - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); - return apiErrorArrayHandler.doErrorResponse(); - } - } - async getAndResponseJpeg() { try { From 553c605463a25c048671eee7b056bc27082cc47c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 1 Jan 2024 20:33:02 +0800 Subject: [PATCH 279/365] refactor: split `getAndResponseJpeg` into two responsibility things - remove `getAndResponseJpeg` method in wado uri service - for right responsibility: - response should in controller, not in service - so, move `setHeader` and `response.end` into controller --- api/WADO-URI/controller/retrieveInstance.js | 8 +++++++- api/WADO-URI/service/WADO-URI.service.js | 19 ------------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/api/WADO-URI/controller/retrieveInstance.js b/api/WADO-URI/controller/retrieveInstance.js index 7dafb77c..deeeaf03 100644 --- a/api/WADO-URI/controller/retrieveInstance.js +++ b/api/WADO-URI/controller/retrieveInstance.js @@ -2,6 +2,7 @@ const { WadoUriService } = require("@api/WADO-URI/service/WADO-URI.service"); const { Controller } = require("../../controller.class"); const { ApiLogger } = require("../../../utils/logs/api-logger"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); class RetrieveSingleInstanceController extends Controller { constructor(req, res) { @@ -25,12 +26,17 @@ class RetrieveSingleInstanceController extends Controller { this.response.setHeader("Content-Type", "application/dicom"); dicomInstanceReadStream.pipe(this.response); } else if (contentType === "image/jpeg") { - this.service.getAndResponseJpeg(); + let jpegBuffer = await this.service.handleRequestQueryAndGetJpeg(); + + this.response.setHeader("Content-Type", "image/jpeg"); + + this.response.end(jpegBuffer, "buffer"); } this.response.on("finish", () => this.service.auditInstanceTransferred()); } catch (e) { + this.auditInstanceTransferred(EventOutcomeIndicator.MajorFailure); let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.logger, e); return apiErrorArrayHandler.doErrorResponse(); } diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 8052bdef..5bf7a2d3 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -28,25 +28,6 @@ class WadoUriService { this.auditBeginTransferring(); } - async getAndResponseJpeg() { - try { - - let jpegBuffer = await this.handleRequestQueryAndGetJpeg(); - - this.response.setHeader("Content-Type", "image/jpeg"); - - this.response.end(jpegBuffer, "buffer"); - this.auditInstanceTransferred(); - - } catch (e) { - this.auditInstanceTransferred(EventOutcomeIndicator.MajorFailure); - - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); - return apiErrorArrayHandler.doErrorResponse(); - } - } - - /** * @throws {NotFoundInstanceError} * @returns {Promise} From d99f68a8994a52a173faa37ede6442af9111cd0a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 1 Jan 2024 20:44:03 +0800 Subject: [PATCH 280/365] refactor: split `getThumbnailAndResponse` into two responsibility things - remove `getThumbnailAndResponse` method in wado uri service - for right responsibility: - response should in controller, not in service - so, move `response.end` into controller - in service, throw error instead of response --- .../WADO-RS/service/thumbnail.service.js | 25 ++++++------------- .../WADO-RS/thumbnail/base.controller.js | 16 ++++-------- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 539f877a..d18ec8a1 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -3,6 +3,7 @@ const errorResponse = require("../../../../../utils/errorResponse/errorResponseM const renderedService = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const _ = require("lodash"); const { getUidsString } = require("./WADO-RS.service"); +const { NotFoundInstanceError } = require("@error/dicom-instance"); class ThumbnailService { /** @@ -18,21 +19,19 @@ class ThumbnailService { this.apiLogger = apiLogger; } - async getThumbnailAndResponse() { + async getThumbnail() { if (!_.get(this.request, "query.viewport")) { _.set(this.request, "query.viewport", "100,100"); } let instanceFramesObj = await this.thumbnailFactory.getThumbnailInstance(); - if (this.checkInstanceExists(instanceFramesObj)) { - return; - } + this.checkInstanceExists(instanceFramesObj); let thumbnail = await this.getThumbnailByInstance(instanceFramesObj); - if (thumbnail) { - return this.response.end(thumbnail, "binary"); + if (!thumbnail) { + throw new Error(`Can not process this image, instanceUID: ${instanceFramesObj.instanceUID}`); } - throw new Error(`Can not process this image, instanceUID: ${instanceFramesObj.instanceUID}`); + return thumbnail; } async getThumbnailByInstance(instanceFramesObj) { @@ -57,18 +56,8 @@ class ThumbnailService { checkInstanceExists(instanceFramesObj) { if (!instanceFramesObj) { - this.response.writeHead(404, { - "Content-Type": "application/dicom+json" - }); - let notFoundMessage = errorResponse.getNotFoundErrorMessage(`Not Found, ${getUidsString(this.thumbnailFactory.uids)}`); - - let notFoundMessageStr = JSON.stringify(notFoundMessage); - - this.apiLogger.logger.warn(`[${notFoundMessageStr}]`); - - return this.response.end(notFoundMessageStr); + throw new NotFoundInstanceError(`Not Found, ${getUidsString(this.thumbnailFactory.uids)}`); } - return undefined; } } diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js index c657a3ba..c96cb0df 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js @@ -2,6 +2,7 @@ const { Controller } = require("@root/api/controller.class"); const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); const { ThumbnailService } = require("../service/thumbnail.service"); const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseThumbnailController extends Controller { constructor(req, res) { @@ -19,18 +20,11 @@ class BaseThumbnailController extends Controller { try { this.logAction(); let thumbnailService = new ThumbnailService(this.request, this.response, this.apiLogger, this.factory); - return thumbnailService.getThumbnailAndResponse(); + let thumbnail = await thumbnailService.getThumbnail(); + return this.response.end(thumbnail, "binary"); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - this.apiLogger.logger.error(errorStr); - - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end({ - code: 500, - message: "An exception occurred" - }); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } From 5aca989ce71984ec081d40c97b5ab8b2df371cd1 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Tue, 2 Jan 2024 14:20:06 +0800 Subject: [PATCH 281/365] fix: `auditInstanceTransferred` call in controller missing `service` to call auditInstanceTransferred. --- api/WADO-URI/controller/retrieveInstance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/WADO-URI/controller/retrieveInstance.js b/api/WADO-URI/controller/retrieveInstance.js index deeeaf03..75e1acf2 100644 --- a/api/WADO-URI/controller/retrieveInstance.js +++ b/api/WADO-URI/controller/retrieveInstance.js @@ -36,7 +36,7 @@ class RetrieveSingleInstanceController extends Controller { this.response.on("finish", () => this.service.auditInstanceTransferred()); } catch (e) { - this.auditInstanceTransferred(EventOutcomeIndicator.MajorFailure); + this.service.auditInstanceTransferred(EventOutcomeIndicator.MajorFailure); let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.logger, e); return apiErrorArrayHandler.doErrorResponse(); } From f547447346652484c8335640d3c54fc38fe8c387 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 3 Jan 2024 14:49:39 +0800 Subject: [PATCH 282/365] feat: use `dicomjson-to-fhir` instead of original handwritten functions --- models/FHIR/DICOM/DICOMToFHIR.js | 22 +- models/FHIR/DICOM/DICOMToFHIREndpoint.js | 35 -- models/FHIR/DICOM/DICOMToFHIRImagingStudy.js | 364 ------------------- models/FHIR/DICOM/DICOMToFHIRPatient.js | 202 ---------- package-lock.json | 61 ++++ package.json | 1 + 6 files changed, 70 insertions(+), 615 deletions(-) delete mode 100644 models/FHIR/DICOM/DICOMToFHIREndpoint.js delete mode 100644 models/FHIR/DICOM/DICOMToFHIRImagingStudy.js delete mode 100644 models/FHIR/DICOM/DICOMToFHIRPatient.js diff --git a/models/FHIR/DICOM/DICOMToFHIR.js b/models/FHIR/DICOM/DICOMToFHIR.js index e48c7466..67bb3b1d 100644 --- a/models/FHIR/DICOM/DICOMToFHIR.js +++ b/models/FHIR/DICOM/DICOMToFHIR.js @@ -3,10 +3,8 @@ const axios = require("axios").default; const _ = require("lodash"); const { urlJoin } = require("../../../utils/url"); const { fhirLogger } = require("../../../utils/logs/log"); -const { dicomJsonToFHIRImagingStudy } = require("./DICOMToFHIRImagingStudy"); -const { dicomJsonToFHIRPatient } = require("./DICOMToFHIRPatient"); -const { dicomJsonToFHIREndpoint } = require("./DICOMToFHIREndpoint"); const { getModalitiesInStudy } = require("@dbModels/instance.model"); +const { DicomJsonToFhir } = require("dicomjson-to-fhir"); class DICOMFHIRConverter { constructor() { @@ -32,19 +30,15 @@ class DICOMFHIRConverter { * @param {JSON} dicomJson The DICOM Json model */ async dicomJsonToFHIR(dicomJson) { - let patient = dicomJsonToFHIRPatient(dicomJson); - let imagingStudy = dicomJsonToFHIRImagingStudy(dicomJson); - if (!imagingStudy.subject.reference.includes(patient.id)) { - imagingStudy.subject.reference = `Patient/${patient.id}`; - } - let endpoint = dicomJsonToFHIREndpoint( + this.dicomFHIR = new DicomJsonToFhir( + dicomJson, this.dicomWeb.retrieveStudiesUrl, this.dicomWeb.name - ); - this.dicomFHIR.patient = patient; - this.dicomFHIR.imagingStudy = imagingStudy; + ).getFhirJson(); + if (!this.dicomFHIR.imagingStudy.subject.reference.includes(this.dicomFHIR.patient.id)) { + this.dicomFHIR.imagingStudy.subject.reference = `Patient/${this.dicomFHIR.patient.id}`; + } await this.setModalitiesInStudy(dicomJson); - this.dicomFHIR.endpoint = endpoint; this.dicomFHIR.imagingStudy.endpoint = [ { reference: `Endpoint/${this.dicomFHIR.endpoint.id}`, @@ -61,7 +55,7 @@ class DICOMFHIRConverter { }); if (modalitiesInStudy.length > 0) { let modalitiesInStudyValue = modalitiesInStudy[0]["00080061"].Value; - for(let i = 0 ; i < modalitiesInStudyValue.length; i++) { + for (let i = 0; i < modalitiesInStudyValue.length; i++) { let modality = { system: "http://dicom.nema.org/resources/ontology/DCM", code: modalitiesInStudyValue[i] diff --git a/models/FHIR/DICOM/DICOMToFHIREndpoint.js b/models/FHIR/DICOM/DICOMToFHIREndpoint.js deleted file mode 100644 index d7a1db3f..00000000 --- a/models/FHIR/DICOM/DICOMToFHIREndpoint.js +++ /dev/null @@ -1,35 +0,0 @@ -/*connectionType -dicom-wado-rs -dicom-qido-rs -dicom-stow-rs -dicon-wado-uri -direct-project -*/ - -/** - * - * @param {string} addressUrl The DICOMWeb URL of study level - * @param {*} id The unique id of this DICOMWeb URL of PACS server. - * @returns - */ -function dicomJsonToFHIREndpoint(addressUrl, id) { - let endpoint = { - resourceType: "Endpoint", - status: "active", - id: id, - connectionType: { - system: "http://terminology.hl7.org/CodeSystem/endpoint-connection-type", - code: "dicom-wado-rs" - }, - payloadType: [ - { - text: "DICOM" - } - ], - payloadMimeType: ["application/dicom"], - address: addressUrl - }; - return endpoint; -} - -module.exports.dicomJsonToFHIREndpoint = dicomJsonToFHIREndpoint; diff --git a/models/FHIR/DICOM/DICOMToFHIRImagingStudy.js b/models/FHIR/DICOM/DICOMToFHIRImagingStudy.js deleted file mode 100644 index c08bcf88..00000000 --- a/models/FHIR/DICOM/DICOMToFHIRImagingStudy.js +++ /dev/null @@ -1,364 +0,0 @@ -const dicomParser = require("dicom-parser"); -const moment = require("moment"); -const flatten = require("flat"); -const _ = require("lodash"); -const { dcm2jsonV8 } = require("../../DICOM/dcmtk"); - -/** - * 將Class轉成Json的父類別 - */ -class ToJsonParent { - constructor() {} - toJson() { - return Object.getOwnPropertyNames(this).reduce((a, b) => { - if (this[b]) a[b] = this[b]; - return a; - }, {}); - } -} - -/** - * ImagingStudy類別 - */ -class ImagingStudy extends ToJsonParent { - constructor() { - super(); - this.resourceType = "ImagingStudy"; - this.id = ""; - this.identifier = []; //0..* - this.status = "unknown"; //1..1 code registered | available | cancelled | entered-in-error | unknown - this.modality = undefined; //0..* coding - this.subject = new Reference(); //1..1 reference - this.started = undefined; //0..1 dateTime - this.endpoint = undefined; //0..* Reference don't have this now (This is mean where the DicomWEB server) - this.numberOfSeries = undefined; //0..1 int - this.numberOfInstances = undefined; //0..1 int - this.description = undefined; //0..1 string - this.series = []; //0..* 放置ImagingStudy_Series - } -} - -class ImagingStudy_Series extends ToJsonParent { - constructor() { - super(); - this.uid = ""; //1..1 - this.number = undefined; //0..1 int - this.modality = new Coding(); //1..1 coding // - this.modality.system = "http://dicom.nema.org/resources/ontology/DCM"; - this.description = undefined; //0..1 string - this.numberOfInstances = ""; //0..1 int - this.endpoint = undefined; //0..* Reference - this.bodySite = undefined; //0..1 Coding - this.laterality = undefined; - this.started = undefined; //0..1 dateTime - this.performer = undefined; //0..* {function (Codeable) :0..1, actor:1..1 (Reference)} - this.instance = []; //0..* - } -} - -class ImagingStudy_Series_Instance extends ToJsonParent { - constructor() { - super(); - this.uid = ""; //1..1 - this.sopClass = new Coding(); //1..1 coding - this.number = ""; //0..1 - this.title = undefined; //0..1 - } -} - -class Coding { - constructor() { - this.system = undefined; - this.version = undefined; - this.code = undefined; - this.display = undefined; - this.userSelected = undefined; - } -} - -class Identifier { - constructor() { - this.use = undefined; - this.type = undefined; - this.system = undefined; - this.value = undefined; - this.period = undefined; - } -} - -class Reference { - constructor() { - this.reference = undefined; //(Literal reference, Relative, internal or absolute URL)(string) - this.type = undefined; //string - this.identifier = undefined; - this.display = undefined; - } -} - -class CodeableConcept { - constructor() { - this.Coding = []; - this.text = undefined; - } -} - -class Period { - constructor() { - this.start = undefined; - this.end = undefined; - } -} - -/** - * - * @param {string} filename The filename of DICOM. - * @return {JSON} - */ -function dicomFileToFHIRImagingStudy(filename) { - try { - let dataset = dicomParser.parseDicom(filename); - let studyobj = new ImagingStudy(); - let studyInstanceUID = dataset.string("x0020000d"); - let ANandIssuer = getDicomParserTagStringConcat( - dataset, - "x00080050", - "x00080051" - ); - studyobj.id = studyInstanceUID; - let identifiers = [ - studyInstanceUID, - ANandIssuer, - dataset.string("x00200010") - ]; - studyobj.identifier = getStudyIdentifiers(identifiers); - studyobj.modality = dataset.string("x00080061"); - let patientId = dataset.string("x00100020"); - if (patientId) { - studyobj.subject.reference = - "Patient/" + dataset.string("x00100020"); - studyobj.subject.type = "Patient"; - studyobj.subject.identifier.use = "usual"; - studyobj.subject.identifier.value = dataset.string("x00100020"); - } else { - studyobj.subject.reference = "Patient/unknown"; - studyobj.subject.type = "Patient"; - studyobj.subject.identifier.use = "anonymous"; - studyobj.subject.identifier.value = "unknown"; - } - - let imaging_started = - dataset.string("x00080020") + dataset.string("x00080030"); - const date = moment(imaging_started, "YYYYMMDDhhmmss").toISOString(); - studyobj.started = date; - studyobj.numberOfSeries = dataset.string("x00201206"); - studyobj.numberOfInstances = dataset.string("x00201208"); - studyobj.description = dataset.string("x00081030"); - let study_series_obj = new ImagingStudy_Series(); - study_series_obj.uid = dataset.string("x0020000e"); - study_series_obj.number = dataset.intString("x00200011"); - study_series_obj.modality.code = dataset.string("x00080060"); - study_series_obj.description = dataset.string("x0008103e"); - study_series_obj.numberOfInstances = dataset.intString("x00201209"); - study_series_obj.bodySite.display = dataset.string("x00180015"); - let series_started = - dataset.string("x00080021") + dataset.string("x00080031"); - const series_date = moment(series_started, "YYYYMMDDhhmmss").toDate(); - study_series_obj.started = - series_date != null ? series_date : undefined; - study_series_obj.performer = - dataset.string("x00081050") || - dataset.string("x00081052") || - dataset.string("x00081070") || - dataset.string("x00081072"); - let study_series_insatance_obj = new ImagingStudy_Series_Instance(); - study_series_insatance_obj.uid = dataset.string("x00080018"); - study_series_insatance_obj.sopClass.system = "urn:ietf:rfc:3986"; - study_series_insatance_obj.sopClass.code = - "urn:oid:" + dataset.string("x00080016"); - study_series_insatance_obj.number = dataset.intString("x00200013"); - study_series_insatance_obj.title = - dataset.string("x00080008") || - dataset.string("x00070080") || - (dataset.string("x0040a043") != undefined - ? dataset.string("x0040a043") + dataset.string("x00080104") - : undefined) || - dataset.string("x00420010"); - let imagingStudyJson = combineImagingStudyClass( - studyobj, - study_series_obj, - study_series_insatance_obj - ); - return imagingStudyJson; - } catch (e) { - console.error(e); - throw e; - } -} - -/** - * - * @param {JSON} dicomJson - * @return {JSON} - */ -function dicomJsonToFHIRImagingStudy(dicomJson) { - //#region study - let studyObj = new ImagingStudy(); - let studyInstanceUID = dcm2jsonV8.dcmString(dicomJson, "0020000D"); - - let ANandIssuer = getTagStringConcat(dicomJson, "00080050", "00080051"); - studyObj.id = studyInstanceUID; - let identifiers = [ - studyInstanceUID, - ANandIssuer, - dcm2jsonV8.dcmString(dicomJson, "00200010") - ]; - studyObj.identifier = getStudyIdentifiers(identifiers); - - let patientId = dcm2jsonV8.dcmString(dicomJson, "00100020"); - studyObj.subject.identifier = new Identifier(); - if (patientId) { - studyObj.subject.reference = - "Patient/" + dcm2jsonV8.dcmString(dicomJson, "00100020"); - studyObj.subject.type = "Patient"; - studyObj.subject.identifier.use = "usual"; - studyObj.subject.identifier.value = dcm2jsonV8.dcmString( - dicomJson, - "00100020" - ); - } else { - studyObj.subject.reference = "Patient/unknown"; - studyObj.subject.type = "Patient"; - studyObj.subject.identifier.use = "anonymous"; - studyObj.subject.identifier.value = "unknown"; - } - - let studyStartedStr = - dcm2jsonV8.dcmString(dicomJson, "00080020") + - dcm2jsonV8.dcmString(dicomJson, "00080030"); - studyObj.started = moment(studyStartedStr, "YYYYMMDDhhmmss").toISOString(); - studyObj.numberOfSeries = dcm2jsonV8.dcmString(dicomJson, "00201206"); - studyObj.numberOfInstances = dcm2jsonV8.dcmString(dicomJson, "00201208"); - studyObj.description = dcm2jsonV8.dcmString(dicomJson, "00081030"); - //#endregion - - //#region series - let seriesObj = new ImagingStudy_Series(); - seriesObj.uid = dcm2jsonV8.dcmString(dicomJson, "0020000E"); - seriesObj.number = dcm2jsonV8.dcmString(dicomJson, "00200011"); - seriesObj.modality.code = dcm2jsonV8.dcmString(dicomJson, "00080060"); - seriesObj.description = dcm2jsonV8.dcmString(dicomJson, "0008103E"); - seriesObj.numberOfInstances = dcm2jsonV8.dcmString(dicomJson, "00201209"); - seriesObj.bodySite = new Coding(); - seriesObj.bodySite.display = dcm2jsonV8.dcmString(dicomJson, "00180015"); - - let seriesStarted = - dcm2jsonV8.dcmString(dicomJson, "00080021") + - dcm2jsonV8.dcmString(dicomJson, "00080031"); - seriesObj.started = moment(seriesStarted, "YYYYMMDDhhmmss"); - seriesObj.started = seriesObj.started.isValid() - ? seriesObj.started.toISOString() - : undefined; - seriesObj.performer = - dcm2jsonV8.dcmString(dicomJson, "00081050") || - dcm2jsonV8.dcmString(dicomJson, "00081052") || - dcm2jsonV8.dcmString(dicomJson, "00081070") || - dcm2jsonV8.dcmString(dicomJson, "00081072"); - //#endregion - - //#region instance - - let instanceObj = new ImagingStudy_Series_Instance(); - instanceObj.uid = dcm2jsonV8.dcmString(dicomJson, "00080018"); - instanceObj.sopClass.system = "urn:ietf:rfc:3986"; - instanceObj.sopClass.code = - "urn:oid:" + dcm2jsonV8.dcmString(dicomJson, "00080016"); - instanceObj.number = dcm2jsonV8.dcmString(dicomJson, "00200013"); - instanceObj.title = - dcm2jsonV8.dcmString(dicomJson, "00080008") || - dcm2jsonV8.dcmString(dicomJson, "00070080") || - (dcm2jsonV8.dcmString(dicomJson, "0040a043") != undefined - ? dcm2jsonV8.dcmString(dicomJson, "0040a043") + - dcm2jsonV8.dcmString(dicomJson, "00080104") - : undefined) || - dcm2jsonV8.dcmString(dicomJson, "00420010"); - //#endregion - - let imagingStudyJson = combineImagingStudyClass( - studyObj, - seriesObj, - instanceObj - ); - return imagingStudyJson; -} - -function getTagStringConcat(dicomJson, ...tags) { - let result = ""; - for (let i = 0; i < tags.length; i++) { - let tag = tags[i]; - let tagValue = dcm2jsonV8.dcmString(dicomJson, tag); - if (tagValue) result += tagValue; - } - if (!result) return undefined; - return result; -} - -function getDicomParserTagStringConcat(dataSet, ...tags) { - let result = ""; - for (let i = 0; i < tags.length; i++) { - let tag = tags[i]; - let tagValue = dataSet.string(tag); - if (tagValue) result += tagValue; - } - if (!result) return undefined; - return result; -} - -function getStudyIdentifiers(identifiers) { - let result = []; - if (identifiers[0] != undefined) { - let identifier1 = new Identifier(); - identifier1.use = "official"; - identifier1.system = "urn:dicom:uid"; - identifier1.value = "urn:oid:" + identifiers[0]; - result.push(identifier1); - } - //need sample dicom with the organization - if (identifiers[1] != undefined) { - let identifier2 = new Identifier(); - identifier2.type = new Coding(); - identifier2.use = "usual"; - identifier2.value = identifiers[1]; - result.push(identifier2); - } - if (identifiers[2] != undefined) { - let identifier3 = new Identifier(); - identifier3.use = "secondary"; - identifier3.value = "s" + identifiers[2]; - result.push(identifier3); - } - return result; -} - -function combineImagingStudyClass( - imagingStudy, - imagingStudySeries, - imagingStudySeriesInstance -) { - try { - let imagingStudyJson = imagingStudy.toJson(); - let seriesJson = imagingStudySeries.toJson(); - let instanceJson = imagingStudySeriesInstance.toJson(); - seriesJson.instance.push(instanceJson); - imagingStudyJson.series.push(seriesJson); - let flattenData = flatten(imagingStudyJson); - flattenData = _.pickBy(flattenData, _.identity); - imagingStudyJson = flatten.unflatten(flattenData); - return imagingStudyJson; - } catch (e) { - console.error(e); - return false; - } -} - -module.exports.dicomJsonToFHIRImagingStudy = dicomJsonToFHIRImagingStudy; -module.exports.dicomFileToFHIRImagingStudy = dicomFileToFHIRImagingStudy; diff --git a/models/FHIR/DICOM/DICOMToFHIRPatient.js b/models/FHIR/DICOM/DICOMToFHIRPatient.js deleted file mode 100644 index fd6bd03c..00000000 --- a/models/FHIR/DICOM/DICOMToFHIRPatient.js +++ /dev/null @@ -1,202 +0,0 @@ -const dicomParser = require("dicom-parser"); -const uuid = require("uuid"); -const fsP = require("node:fs/promises"); -const _ = require("lodash"); -const moment = require("moment"); -const { dcm2jsonV8 } = require("../../DICOM/dcmtk"); - -class HumanName { - constructor() { - this.use = "anonymous"; - this.text = undefined; - this.family = undefined; //姓氏 - this.given = undefined; //名字或中間名 - this.prefix = undefined; - this.suffix = undefined; - } - toJson() { - return Object.getOwnPropertyNames(this).reduce((a, b) => { - if (this[b]) a[b] = this[b]; - return a; - }, {}); - } -} - -/** - * - * @param {string} filename The filename of the DICOM. - */ -async function dicomFileToFHIRPatient(filename) { - try { - let dicomFileBuffer = await fsP.readFile(filename); - let dataset = dicomParser.parseDicom(dicomFileBuffer); - let pName = dataset.string("x00100010"); - let pGender = dataset.string("x00100040") || "unknown"; - let FHIRGender = { - M: "male", - F: "female", - O: "other", - UNKNOWN: "unknown" - }; - pGender = FHIRGender[pGender.toUpperCase()]; - let pBD = dataset.string("x00100030"); - let patientName = new HumanName(); - if (pName == undefined) { - pName = "UNKNOWN"; - } else { - patientName.use = "usual"; - } - patientName.text = pName; - let DICOMpName = _.pickBy(dicomParser.parsePN(pName), _.identity); //remove undefined or null key - - patientName = patientName.toJson(); - let pJson = JSON.stringify(patientName); - pJson = JSON.parse(pJson); - let FHIRpName = { - familyName: (pJson) => { - pJson.family = DICOMpName.familyName; - }, - givenName: (pJson) => { - if (pJson.given) { - pJson.given.push(DICOMpName.givenName); - } else { - pJson.given = []; - pJson.given.push(DICOMpName.givenName); - } - }, - middleName: (pJson) => { - if (pJson.given) { - pJson.given.push(DICOMpName.middleName); - } else { - pJson.given = []; - pJson.given.push(DICOMpName.middleName); - } - }, - prefix: (pJson) => { - if (pJson.prefix) { - pJson.prefix.push(DICOMpName.middleName); - } else { - pJson.prefix = []; - pJson.prefix.push(DICOMpName.middleName); - } - }, - suffix: (pJson) => { - if (pJson.prefix) { - pJson.prefix.push(DICOMpName.middleName); - } else { - pJson.prefix = []; - pJson.prefix.push(DICOMpName.middleName); - } - } - }; - for (let key in DICOMpName) { - FHIRpName[key](pJson); - } - let Patient = { - resourceType: "Patient", - id: dataset.string("x00100020") || uuid.v4(), - gender: pGender, - active: true, - name: [pJson] - }; - if (pBD) { - Patient.birthDate = moment.utc(pBD).format("YYYY-MM-DD"); - } - Patient.id = Patient.id.replace(/[\s\u0000]/gim, ""); - Patient.id = Patient.id.replace(/_/gim, ""); - return Patient; - } catch (e) { - console.error(e); - throw e; - } -} - -/** - * - * @param {JSON} dcmJson - * @returns - */ -function dicomJsonToFHIRPatient(dcmJson) { - let pName = dcm2jsonV8.dcmString(dcmJson, "00100010"); - let pGender = dcm2jsonV8.dcmString(dcmJson, "00100040") || "unknown"; - let FHIRGender = { - M: "male", - F: "female", - O: "other", - UNKNOWN: "unknown" - }; - pGender = FHIRGender[pGender.toUpperCase()]; - let pBD = dcm2jsonV8.dcmString(dcmJson, "00100030"); - let patientName = new HumanName(); - if (pName == undefined) { - pName = {}; - _.set(pName, "Alphabetic", "UNKNOWN"); - } else { - patientName.use = "usual"; - } - patientName.text = pName.Alphabetic; - let DICOMpName = _.pickBy( - dicomParser.parsePN(pName.Alphabetic), - _.identity - ); //remove undefined or null key - - patientName = patientName.toJson(); - let pJson = JSON.stringify(patientName); - pJson = JSON.parse(pJson); - let FHIRpName = { - familyName: (pJson) => { - pJson.family = DICOMpName.familyName; - }, - givenName: (pJson) => { - if (pJson.given) { - pJson.given.push(DICOMpName.givenName); - } else { - pJson.given = []; - pJson.given.push(DICOMpName.givenName); - } - }, - middleName: (pJson) => { - if (pJson.given) { - pJson.given.push(DICOMpName.middleName); - } else { - pJson.given = []; - pJson.given.push(DICOMpName.middleName); - } - }, - prefix: (pJson) => { - if (pJson.prefix) { - pJson.prefix.push(DICOMpName.middleName); - } else { - pJson.prefix = []; - pJson.prefix.push(DICOMpName.middleName); - } - }, - suffix: (pJson) => { - if (pJson.prefix) { - pJson.prefix.push(DICOMpName.middleName); - } else { - pJson.prefix = []; - pJson.prefix.push(DICOMpName.middleName); - } - } - }; - for (let key in DICOMpName) { - FHIRpName[key](pJson); - } - let Patient = { - resourceType: "Patient", - id: dcm2jsonV8.dcmString(dcmJson, "00100020") || uuid.v4(), - gender: pGender, - active: true, - name: [pJson] - }; - Patient.id = Patient.id.replace(/[\s\u0000]/gim, ""); - Patient.id = Patient.id.replace(/_/gim, ""); - if (pBD) { - Patient.birthDate = moment.utc(pBD).format("YYYY-MM-DD"); - } - return Patient; -} - -module.exports.dicomJsonToFHIRPatient = dicomJsonToFHIRPatient; -module.exports.dicomFileToFHIRPatient = dicomFileToFHIRPatient; diff --git a/package-lock.json b/package-lock.json index 38c6a898..5248c354 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "cors": "^2.8.5", "dicom-parser": "^1.8.13", "dicom-to-json": "^1.2.1", + "dicomjson-to-fhir": "^1.0.1", "dotenv": "^16.0.3", "env-var": "^7.3.1", "express": "^4.18.2", @@ -1376,6 +1377,14 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "engines": { + "node": ">=8" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", @@ -3608,6 +3617,11 @@ "node": "*" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3786,6 +3800,15 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" }, + "node_modules/dicomjson-to-fhir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dicomjson-to-fhir/-/dicomjson-to-fhir-1.0.1.tgz", + "integrity": "sha512-V3YlmOBp30RDk3t04GwHa/ZlbgMUnNrV06AEtBgZyENWYhrq2awLaOWKdo6s3/fT4f1Pha/qK/9VluRogUp1yw==", + "dependencies": { + "dayjs": "^1.11.10", + "uid": "^2.0.2" + } + }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -9229,6 +9252,17 @@ "node": ">=0.8.0" } }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -10647,6 +10681,11 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==" + }, "@mapbox/node-pre-gyp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", @@ -12443,6 +12482,11 @@ "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -12573,6 +12617,15 @@ } } }, + "dicomjson-to-fhir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dicomjson-to-fhir/-/dicomjson-to-fhir-1.0.1.tgz", + "integrity": "sha512-V3YlmOBp30RDk3t04GwHa/ZlbgMUnNrV06AEtBgZyENWYhrq2awLaOWKdo6s3/fT4f1Pha/qK/9VluRogUp1yw==", + "requires": { + "dayjs": "^1.11.10", + "uid": "^2.0.2" + } + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -16678,6 +16731,14 @@ "dev": true, "optional": true }, + "uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "requires": { + "@lukeed/csprng": "^1.0.0" + } + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", diff --git a/package.json b/package.json index 65459e55..f9bc154b 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "cors": "^2.8.5", "dicom-parser": "^1.8.13", "dicom-to-json": "^1.2.1", + "dicomjson-to-fhir": "^1.0.1", "dotenv": "^16.0.3", "env-var": "^7.3.1", "express": "^4.18.2", From 432ce6ba378760c18949e4191f6cbd2a1c18fe59 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 3 Jan 2024 15:36:40 +0800 Subject: [PATCH 283/365] fix: set header when response sent in ApiErrorHandler - Use chain fn instead of `writeHead` to prevent `.json` set header two times --- error/api-errors.handler.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/error/api-errors.handler.js b/error/api-errors.handler.js index bcb2c0b6..9ca10fda 100644 --- a/error/api-errors.handler.js +++ b/error/api-errors.handler.js @@ -90,10 +90,7 @@ class ApiErrorArrayHandler { apiLogger.logger.error(e); if (!response.headersSent) { - response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return response.json(getInternalServerErrorMessage("An exception occurred")); + return response.status(500).set("content-type", "application/dicom+json").send(getInternalServerErrorMessage("An exception occurred")); } return response.end(); } From 8d9adb35c9bb9c4dd1efafc5fb2a61918a7dbae7 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 3 Jan 2024 19:41:47 +0800 Subject: [PATCH 284/365] feat: add dicom convert to fhir API --- api/fhir-convert/controller/dicom-to-fhir.js | 38 +++++++++++++ .../service/fhir-convert.service.js | 38 +++++++++++++ api/fhir-convert/index.js | 54 +++++++++++++++++++ docs/swagger/openapi.json | 40 +++++++++++++- routes.js | 2 + 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 api/fhir-convert/controller/dicom-to-fhir.js create mode 100644 api/fhir-convert/controller/service/fhir-convert.service.js create mode 100644 api/fhir-convert/index.js diff --git a/api/fhir-convert/controller/dicom-to-fhir.js b/api/fhir-convert/controller/dicom-to-fhir.js new file mode 100644 index 00000000..9bba08db --- /dev/null +++ b/api/fhir-convert/controller/dicom-to-fhir.js @@ -0,0 +1,38 @@ +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); +const { Controller } = require("@root/api/controller.class"); +const { FhirConvertService } = require("./service/fhir-convert.service"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); + + +class FhirConvertController extends Controller { + constructor(req, res) { + super(req, res); + this.apiLogger = new ApiLogger(req, "fhir-convert"); + this.apiLogger.addTokenValue(); + } + + async mainProcess() { + let fhirConvertService = new FhirConvertService(this.request, this.response); + try { + let fhirJson = await fhirConvertService.convert(); + return this.response + .set("content-type", "application/json") + .status(200) + .json(fhirJson); + } catch (e) { + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); + } + } +} + +/** + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ +module.exports = async function (req, res) { + let controller = new FhirConvertController(req, res); + + await controller.doPipeline(); +}; diff --git a/api/fhir-convert/controller/service/fhir-convert.service.js b/api/fhir-convert/controller/service/fhir-convert.service.js new file mode 100644 index 00000000..75df5cc2 --- /dev/null +++ b/api/fhir-convert/controller/service/fhir-convert.service.js @@ -0,0 +1,38 @@ +const { DicomJsonToFhir } = require("dicomjson-to-fhir"); +const dicomToJson = require("../dicom-to-fhir"); +const { dcm2jsonV8 } = require("@models/DICOM/dcmtk"); +const { raccoonConfig } = require("@root/config-class"); +const Joi = require("joi"); +const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); + +const fileSchema = Joi.object({ + files: Joi.object({ + file: Joi.object({ + filepath: Joi.string().required() + }).required() + }).required() +}); + +class FhirConvertService { + constructor(req, res) { + this.request = req; + this.response = res; + } + + async convert() { + let { value, error } = fileSchema.validate(this.request, { allowUnknown : true}); + if (error) { + throw new DicomWebServiceError(DicomWebStatusCodes.InvalidArgumentValue, error.details[0].message, 400); + } + let dicomJson = await dcm2jsonV8.exec(this.request.files.file.filepath); + let protocol = this.request.secure ? "https" : "http"; + let dicomJsonToFhir = new DicomJsonToFhir( + dicomJson, + "raccoon-dicom-web-server", + `${protocol}://${this.request.headers.host}/${raccoonConfig.dicomWebConfig.apiPath}/studies` + ); + return dicomJsonToFhir.getFhirJson(); + } +} + +module.exports.FhirConvertService = FhirConvertService; \ No newline at end of file diff --git a/api/fhir-convert/index.js b/api/fhir-convert/index.js new file mode 100644 index 00000000..03a66f1f --- /dev/null +++ b/api/fhir-convert/index.js @@ -0,0 +1,54 @@ +/** + * Route /fhir-convert + * Implement `DICOM convert to FHIR ImagingStudy, Patient, Endpoint` + * + * @author Chin-Lin Lee + */ + +const Joi = require("joi"); +const { validateByJoi } = require("../validator"); +const express = require("express"); +const router = express.Router(); +const formidable = require("formidable"); + +const formMiddleWare = async (req, res, next) => { + const form = formidable({}); + + form.parse(req, (err, fields, files) => { + if (err) { + next(err); + return; + } + req.fields = fields; + req.files = files; + next(); + }); +}; + +/** + * @openapi + * /fhir-convert: + * post: + * tags: + * - fhir-convert + * description: Convert DICOM to FHIR ImagingStudy, Patient, Endpoint + * requestBody: + * content: + * multipart/form-data: + * schema: + * type: object + * properties: + * file: + * type: string + * format: binary + * encoding: + * file: + * contentType: application/dicom; + * responses: + * "200": + * description: The DICOM instance store successfully + */ +router.post("/", formMiddleWare, require("./controller/dicom-to-fhir")); + + +module.exports = router; \ No newline at end of file diff --git a/docs/swagger/openapi.json b/docs/swagger/openapi.json index 607403b8..9265bb51 100644 --- a/docs/swagger/openapi.json +++ b/docs/swagger/openapi.json @@ -152,7 +152,12 @@ "content": { "application/dicom+json": { "schema": { - "$ref": "#/components/schemas/PatientRequiredMatchingAttributes" + "type": "object", + "properties": { + "patientID": { + "type": "string" + } + } } } } @@ -1298,6 +1303,39 @@ } } }, + "/fhir-convert": { + "post": { + "tags": [ + "fhir-convert" + ], + "description": "Convert DICOM to FHIR ImagingStudy, Patient, Endpoint", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + }, + "encoding": { + "file": { + "contentType": "application/dicom;" + } + } + } + } + }, + "responses": { + "200": { + "description": "The DICOM instance store successfully" + } + } + } + }, "/wado": { "get": { "tags": [ diff --git a/routes.js b/routes.js index e6997140..c11fe1e4 100644 --- a/routes.js +++ b/routes.js @@ -32,4 +32,6 @@ module.exports = function (app) { app.use("/dicom-web", require("./api/dicom-web/pam-rs.route")); app.use("/wado", require("./api/WADO-URI")); + + app.use("/fhir-convert", require("./api/fhir-convert")); }; From ed744b0be41016eee0d208284160a5d852211f20 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 4 Jan 2024 20:02:04 +0800 Subject: [PATCH 285/365] refactor: rename `create-mwlitem` to `create-mwlItem` --- .../controller/MWL-RS/service/create-mwlItem.service.js | 2 +- api/dicom-web/controller/MWL-RS/create-mwlItem.js | 2 +- .../{create-mwlitem.service.js => create-mwlItem.service.js} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename api/dicom-web/controller/MWL-RS/service/{create-mwlitem.service.js => create-mwlItem.service.js} (100%) diff --git a/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js index b9e30767..ab445f8d 100644 --- a/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js +++ b/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js @@ -1,6 +1,6 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); const { PatientModel } = require("@models/sql/models/patient.model"); -const { CreateMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service"); +const { CreateMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { MwlItemPersistentObject } = require("@models/sql/po/mwlItem.po"); const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); diff --git a/api/dicom-web/controller/MWL-RS/create-mwlItem.js b/api/dicom-web/controller/MWL-RS/create-mwlItem.js index 152b3966..5a7e3ae3 100644 --- a/api/dicom-web/controller/MWL-RS/create-mwlItem.js +++ b/api/dicom-web/controller/MWL-RS/create-mwlItem.js @@ -1,7 +1,7 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); const { Controller } = require("@root/api/controller.class"); const { ApiLogger } = require("@root/utils/logs/api-logger"); -const { CreateMwlItemService } = require("@api/dicom-web/controller/MWL-RS/service/create-mwlitem.service"); +const { CreateMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service"); class CreateMwlItemController extends Controller { constructor(req, res) { diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js similarity index 100% rename from api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js rename to api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js From b23d78a56c4f33745fcad6f9b5eebbd4547332f6 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 4 Jan 2024 20:40:33 +0800 Subject: [PATCH 286/365] fix(dimse): the issue of file path in different operating systems --- dimse-sql/utils.js | 8 +++++--- dimse/utils.js | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/dimse-sql/utils.js b/dimse-sql/utils.js index a090c17c..5c302e64 100644 --- a/dimse-sql/utils.js +++ b/dimse-sql/utils.js @@ -46,9 +46,11 @@ async function getInstancesFromKeysAttr(keys) { for (let instance of instances) { let instanceFile = await File.newInstanceAsync( - path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - instance.instancePath + path.resolve( + path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + instance.instancePath + ) ) ); diff --git a/dimse/utils.js b/dimse/utils.js index beca3753..67cef4a4 100644 --- a/dimse/utils.js +++ b/dimse/utils.js @@ -45,9 +45,11 @@ async function getInstancesFromKeysAttr(keys) { for (let instance of instances) { let instanceFile = await File.newInstanceAsync( - path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - instance.instancePath + path.resolve( + path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + instance.instancePath + ) ) ); From 766fe20963e2347b6013de1319db53acf4c0b710 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 6 Jan 2024 13:26:05 +0800 Subject: [PATCH 287/365] fix: missing mongodb env example --- .env.template | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.env.template b/.env.template index 2019778c..ddf43b82 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,13 @@ +# MongoDB +MONGODB_NAME="raccoon" +MONGODB_HOSTS=["127.0.0.1"] +MONGODB_PORTS=[27017] +MONGODB_USER="root" +MONGODB_PASSWORD="root" +MONGODB_AUTH_SOURCE="admin" +MONGODB_OPTIONS="" +MONGODB_IS_SHARDING_MODE=false + # SQL SQL_HOST="127.0.0.1" SQL_PORT="5432" From 1c7bf4acaeec79400705fc6cd8d1b653fa18329b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 6 Jan 2024 13:51:22 +0800 Subject: [PATCH 288/365] feat: query image that in recycling status (delete status 1) --- .../controller/QIDO-RS/service/QIDO-RS.service.js | 7 ++++++- api/dicom-web/qido-rs.route.js | 3 ++- models/mongodb/schema/dicom.schema.js | 2 +- utils/typeDef/dicom.js | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js index 78188d00..caeb450f 100644 --- a/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ b/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js @@ -56,6 +56,10 @@ class QidoRsService { delete this.request.query["includefield"]; + /** @private */ + this.isRecycle = Boolean(this.request.query?.isRecycle); + delete this.request.query?.isRecycle; + this.initQuery_(); } @@ -89,7 +93,8 @@ class QidoRsService { limit: this.limit_, includeFields: this.includeFields_, retrieveBaseUrl: `${dicomWebService.getBasicURL()}/studies`, - requestParams: this.request.params + requestParams: this.request.params, + isRecycle: this.isRecycle }; queryAudit.onQuery( diff --git a/api/dicom-web/qido-rs.route.js b/api/dicom-web/qido-rs.route.js index 5fc87490..c477863a 100644 --- a/api/dicom-web/qido-rs.route.js +++ b/api/dicom-web/qido-rs.route.js @@ -22,7 +22,8 @@ const queryValidation = { return convertKeywordToHex(attribute); } ) - ).single() + ).single(), + isRecycle: Joi.boolean().default(false) }; /** diff --git a/models/mongodb/schema/dicom.schema.js b/models/mongodb/schema/dicom.schema.js index 24ac0ecb..6a83c692 100644 --- a/models/mongodb/schema/dicom.schema.js +++ b/models/mongodb/schema/dicom.schema.js @@ -78,7 +78,7 @@ class DicomSchemaOptionsFactory { let docs = await mongoose.model(DicomModelNames[level]).find({ ...queryOptions.query, deleteStatus: { - $eq: 0 + $eq: queryOptions.isRecycle ? 1 : 0 } }, projection) .limit(queryOptions.limit) diff --git a/utils/typeDef/dicom.js b/utils/typeDef/dicom.js index f9aeb60d..9e6c32cf 100644 --- a/utils/typeDef/dicom.js +++ b/utils/typeDef/dicom.js @@ -23,6 +23,7 @@ * @property {string} retrieveBaseUrl * @property {object} requestParams? * @property {string[]} includeFields + * @property {boolean} [isRecycle] */ const DICOM = true; From 1c8350e611226ba06893e5adc90b4a45e05d21fd Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 6 Jan 2024 14:14:50 +0800 Subject: [PATCH 289/365] docs: add openapi comments for `isRecycle` --- api/dicom-web/qido-rs.route.js | 6 +++++ docs/swagger/openapi.json | 26 ++++++++++++++++++++ docs/swagger/parameters/dicomweb-common.yaml | 8 +++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/api/dicom-web/qido-rs.route.js b/api/dicom-web/qido-rs.route.js index c477863a..99bd21ee 100644 --- a/api/dicom-web/qido-rs.route.js +++ b/api/dicom-web/qido-rs.route.js @@ -65,6 +65,7 @@ function convertKeywordToHex(attribute) { * - $ref: "#/components/parameters/PatientName" * - $ref: "#/components/parameters/PatientID" * - $ref: "#/components/parameters/StudyID" + * - $ref: "#/components/parameters/isRecycle" * responses: * 200: * description: Query successfully @@ -99,6 +100,7 @@ router.get("/studies", validateParams(queryValidation, "query", { * - $ref: "#/components/parameters/StudyID" * - $ref: "#/components/parameters/Modality" * - $ref: "#/components/parameters/SeriesNumber" + * - $ref: "#/components/parameters/isRecycle" * responses: * 200: * description: Query successfully @@ -139,6 +141,7 @@ router.get( * - $ref: "#/components/parameters/SeriesNumber" * - $ref: "#/components/parameters/SOPClassUID" * - $ref: "#/components/parameters/InstanceNumber" + * - $ref: "#/components/parameters/isRecycle" * responses: * 200: * description: Query successfully @@ -181,6 +184,7 @@ router.get( * - $ref: "#/components/parameters/SeriesNumber" * - $ref: "#/components/parameters/SOPClassUID" * - $ref: "#/components/parameters/InstanceNumber" + * - $ref: "#/components/parameters/isRecycle" * responses: * 200: * description: Query successfully @@ -219,6 +223,7 @@ router.get( * - $ref: "#/components/parameters/StudyID" * - $ref: "#/components/parameters/Modality" * - $ref: "#/components/parameters/SeriesNumber" + * - $ref: "#/components/parameters/isRecycle" * responses: * 200: * description: Query successfully @@ -259,6 +264,7 @@ router.get( * - $ref: "#/components/parameters/SeriesNumber" * - $ref: "#/components/parameters/SOPClassUID" * - $ref: "#/components/parameters/InstanceNumber" + * - $ref: "#/components/parameters/isRecycle" * responses: * 200: * description: Query successfully diff --git a/docs/swagger/openapi.json b/docs/swagger/openapi.json index 9265bb51..a888a248 100644 --- a/docs/swagger/openapi.json +++ b/docs/swagger/openapi.json @@ -274,6 +274,9 @@ }, { "$ref": "#/components/parameters/StudyID" + }, + { + "$ref": "#/components/parameters/isRecycle" } ], "responses": { @@ -367,6 +370,9 @@ }, { "$ref": "#/components/parameters/SeriesNumber" + }, + { + "$ref": "#/components/parameters/isRecycle" } ], "responses": { @@ -438,6 +444,9 @@ }, { "$ref": "#/components/parameters/InstanceNumber" + }, + { + "$ref": "#/components/parameters/isRecycle" } ], "responses": { @@ -515,6 +524,9 @@ }, { "$ref": "#/components/parameters/InstanceNumber" + }, + { + "$ref": "#/components/parameters/isRecycle" } ], "responses": { @@ -580,6 +592,9 @@ }, { "$ref": "#/components/parameters/SeriesNumber" + }, + { + "$ref": "#/components/parameters/isRecycle" } ], "responses": { @@ -648,6 +663,9 @@ }, { "$ref": "#/components/parameters/InstanceNumber" + }, + { + "$ref": "#/components/parameters/isRecycle" } ], "responses": { @@ -1490,6 +1508,14 @@ } } }, + "isRecycle": { + "name": "isRecycle", + "description": "Query if the instance is in recycle bin, default is false, and notice that this one is not standard by DICOM standard (i.e. raccoon custom parameter)", + "in": "query", + "schema": { + "type": "boolean" + } + }, "spsID": { "in": "path", "name": "spsID", diff --git a/docs/swagger/parameters/dicomweb-common.yaml b/docs/swagger/parameters/dicomweb-common.yaml index fb151cfa..f8137957 100644 --- a/docs/swagger/parameters/dicomweb-common.yaml +++ b/docs/swagger/parameters/dicomweb-common.yaml @@ -37,4 +37,10 @@ components: schema: type: array items: - type: string \ No newline at end of file + type: string + "isRecycle": + name: isRecycle + description: "Query if the instance is in recycle bin, default is false, and notice that this one is not standard by DICOM standard (i.e. raccoon custom parameter)" + in: query + schema: + type: boolean \ No newline at end of file From bb4616af5593672bbb549b5d22cd9022c07d9741 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 6 Jan 2024 14:22:00 +0800 Subject: [PATCH 290/365] fix: Add method to increment delete status for hierarchical image deletion # Problems - Series and instances under study are not delete - Instances under series are not deleted # Solutions - Add methods to increment delete status for series and study models to facilitate hierarchical image deletion. This ensures proper update of delete status across related entities. --- models/mongodb/models/series.model.js | 11 +++++++++++ models/mongodb/models/study.model.js | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/models/mongodb/models/series.model.js b/models/mongodb/models/series.model.js index 7d13a295..80d91580 100644 --- a/models/mongodb/models/series.model.js +++ b/models/mongodb/models/series.model.js @@ -13,6 +13,17 @@ let dicomSeriesSchemaOptions = _.merge( DicomSchemaOptionsFactory.get("series", SeriesDocDicomJsonHandler), { methods: { + incrementDeleteStatus: async function () { + await mongoose.model("dicom").updateMany({ + seriesUID: this.seriesUID + }, { + $inc: { + deleteStatus: 1 + } + }); + this.deleteStatus += 1; + await this.save(); + }, deleteDicomInstances: async function () { let seriesPath = this.seriesPath; logger.warn("Permanently delete series folder: " + seriesPath); diff --git a/models/mongodb/models/study.model.js b/models/mongodb/models/study.model.js index 9d804de6..13a28c33 100644 --- a/models/mongodb/models/study.model.js +++ b/models/mongodb/models/study.model.js @@ -13,6 +13,26 @@ let dicomStudySchemaOptions = _.merge( DicomSchemaOptionsFactory.get("study", StudyDocDicomJsonHandler), { methods: { + incrementDeleteStatus: async function() { + await Promise.all([ + mongoose.model("dicomSeries").updateMany({ + studyUID: this.studyUID + }, { + $inc : { + deleteStatus: 1 + } + }), + mongoose.model("dicom").updateMany({ + studyUID: this.studyUID + }, { + $inc: { + deleteStatus: 1 + } + }) + ]); + this.deleteStatus += 1; + await this.save(); + }, deleteDicomInstances: async function () { let studyPath = this.studyPath; From 89e385b01e808d22267a590c28ccc42e58491665 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 7 Jan 2024 23:11:46 +0800 Subject: [PATCH 291/365] feat: use dcm4che dcm2json instead of dcmtk - for more maintainable, we want to drop c++ v8 dcmtk dependency - just use java and javascript to reduce maintenance difficulty --- .../service/fhir-convert.service.js | 5 +- models/DICOM/dcm4che/dcm2json.js | 45 ++++++++++++++++++ .../dcm4chee/lib/qrscp/dcm777-5.29.2.jar | Bin 25930 -> 26194 bytes models/DICOM/dicom-json-parser.js | 5 +- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 models/DICOM/dcm4che/dcm2json.js diff --git a/api/fhir-convert/controller/service/fhir-convert.service.js b/api/fhir-convert/controller/service/fhir-convert.service.js index 75df5cc2..92a6fe7d 100644 --- a/api/fhir-convert/controller/service/fhir-convert.service.js +++ b/api/fhir-convert/controller/service/fhir-convert.service.js @@ -1,9 +1,8 @@ const { DicomJsonToFhir } = require("dicomjson-to-fhir"); -const dicomToJson = require("../dicom-to-fhir"); -const { dcm2jsonV8 } = require("@models/DICOM/dcmtk"); const { raccoonConfig } = require("@root/config-class"); const Joi = require("joi"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { JDcm2Json } = require("@models/DICOM/dcm4che/dcm2json"); const fileSchema = Joi.object({ files: Joi.object({ @@ -24,7 +23,7 @@ class FhirConvertService { if (error) { throw new DicomWebServiceError(DicomWebStatusCodes.InvalidArgumentValue, error.details[0].message, 400); } - let dicomJson = await dcm2jsonV8.exec(this.request.files.file.filepath); + let dicomJson = await JDcm2Json.get(this.request.files.file.filepath); let protocol = this.request.secure ? "https" : "http"; let dicomJsonToFhir = new DicomJsonToFhir( dicomJson, diff --git a/models/DICOM/dcm4che/dcm2json.js b/models/DICOM/dcm4che/dcm2json.js new file mode 100644 index 00000000..2cbdf8dd --- /dev/null +++ b/models/DICOM/dcm4che/dcm2json.js @@ -0,0 +1,45 @@ + +const path = require("path"); +const { importClassAsync } = require("java-bridge"); +const { JsonGenerator } = require("@java-wrapper/javax/json/stream/JsonGenerator"); +const { JSONWriter } = require("@dcm4che/json/JSONWriter"); +const { DicomInputStream } = require("@dcm4che/io/DicomInputStream"); +const { File } = require("@java-wrapper/java/io/File"); +const { Json } = require("@java-wrapper/javax/json/Json"); +const { Common } = require("@java-wrapper/org/github/chinlinlee/dcm777/common/Common"); +const { DicomInputStream$IncludeBulkData } = require("@dcm4che/io/DicomInputStream$IncludeBulkData"); +const { BasicBulkDataDescriptor } = require("@dcm4che/io/BasicBulkDataDescriptor"); + + +class JDcm2Json { + + /** + * + * @param {string} filename + */ + static async get(filename) { + let jFile = new File(filename); + let dicomInputStream = new DicomInputStream(jFile); + await dicomInputStream.setIncludeBulkData(DicomInputStream$IncludeBulkData.NO); + await dicomInputStream.setBulkDataDescriptor(new BasicBulkDataDescriptor()); + + let JByteArrayOutputStream = await importClassAsync("java.io.ByteArrayOutputStream"); + let byteArrayOutputStream = new JByteArrayOutputStream(); + + let jsonGeneratorFactory = await Json.createGeneratorFactory( + await Common.jsonGeneratorFactoryConfig(false) + ); + let jsonGenerator = await jsonGeneratorFactory.createGenerator(byteArrayOutputStream); + let jsonWriter = new JSONWriter(jsonGenerator); + + await dicomInputStream.setDicomInputHandler(jsonWriter); + await dicomInputStream.readDataset(); + await jsonGenerator.flush(); + + let jsonStr = await byteArrayOutputStream.toString("UTF-8"); + await dicomInputStream.close(); + return JSON.parse(jsonStr); + } +} + +module.exports.JDcm2Json = JDcm2Json; \ No newline at end of file diff --git a/models/DICOM/dcm4che/javaNode/dcm4chee/lib/qrscp/dcm777-5.29.2.jar b/models/DICOM/dcm4che/javaNode/dcm4chee/lib/qrscp/dcm777-5.29.2.jar index b7d6f2fef1089f7a01710a0e3927a636777242d0..9d98d135fa80f7fc25bda2cee2a1a46743b164f5 100644 GIT binary patch delta 4213 zcmZWs2{=@38y-WLv70PS*|INV8QUn?O7kgqHmTZ_FZ(K3`R zvL}TwQRq`jr0oAOF?!TR)OCSy!7j z^fB9kX*5NXeG)wcfg&q8sVcA^68N_VhDbMwKW<}YHKsf=n!btQZHb8 zUY}S0sw{aHG5#RHHD1fPg?X}scv>>3Y{eKo$5i*Y#4_^LgQ<%LII!+3D|^S~MnBcQ zvwx7#v4~G8t(NTx!Ibm+^?Y(I^!0B(FX0*G)R`=++{SWV&2>(%8C?Z;D|6smI?r^G z$@}J%o_cdm{(9$a|IW-xeaDi7vn?sjVc*wef%@>u6Jb_>5?U<*C*`msQ6j~j70o3o%kp(MQ!qGeDAEZ6&oJyX%knUjlJ7rB9( zqPJ~rQWr-`PM&f1w)XRjYY_i=pubAt@0d%P9k>MKryNSO{y5t@&-~|tm_zUGT~=Z) zbLL7aWf|>O*WRtbGM(ug7NjV(cGxsa85(lG5g)*LTDvD&cd0g@E&J`r(DKGuS*F#| z)uVpb^b@fs2BI`(TL+7Vjv%E!Gb{Dh_*Bj^J_(44A6W>A_M}Fy2i+Q3X0tY9VRiU= z!>bVrflyf>kev@ZizeOBVl|)}cvcg-(aCB}H$-9jcmZC_`&V^dt(v}iY>8LCzv#buX0uc$bo_dj zo#!<~@AH~S-|DY{NAyKxdDKc7fA zgHh_eOD+wLK7#zW$zKo@!ozI@xC&|9)2g=69u*D-u7a?C0={ z9*vE=*x{e2!{c(Z^sIIr&A9&5G%@IN{U}j-KC{`7F*~GQCnZ4tL^hN2zQWjIe+VK% zAW5Q9Fha8uzBu3{chRyhw}$^qSRdP|DRqGgqle-p=gKb>R`9qtsq%_5k0tzshsnF? zht7Y$cYPkdYvgA2Y0`8RY@e6C#auxi-uS4pIALYfGg*Z|u9sZkQ&LWG_k7kds(!{$ zo;BCiFmm|J;b`?tK>@*c7tRbVprmkz+9@-;J1B z;vMoW8TNUXC4-b9q@J#WaY=j5*k7DTKaLoeaQI8kVvrEi4Zn78&=e{hlNAx4CMj({ z*8#ni({tG`@Xa9KP!iUu<%Ykt3dd3NgtO)e@)V(@fCJb?58c6Up2=x~zwk0TQoS`_ z;G@*g=6r!Vo4uLvwWFpuagzxIRF?00JVG%`023_q@P+P(gVR1F(u*g{v>6y=>nnY# zUG`9QvTn(){=Ua5`J^Rln9q}3`&@F2Q7raSO-Q@vC1&(yL$W7Po-^Z%3FdvYBp>Ny zzT%uS!{ct7CCcSn*> zc_nX;A2*93KO&`eDK3wd9pgTT&d-|0TuHikfO@iYA6H$He(g_JyS__mx9i123gS3x zr1|R~IV4A;v3c3k;ba@lTe7h?_WwMkTmEImsY-)zZ}+jtCmTJXV!ae+yj+mU-6z#y zuWR0ve@s3Vpux=BP|opVagbBV>g59=tQPU^`!A+rm0_k2y6ntzJ^H&w6vf4Y$WS|o zy};)W2c5@v1<1d)#JKcCRn%14h{L~Ju?^8GdkqVvJWC%f=2dkc>KQtw^V2A{c$z(G zK6EVbbps~x(dhSOlep`y7YgyEgJNS-W^6hw(>fN~M5_h=BkaG}b->b9?|&FO?%O}E z)9N5n$L4-@qBNXeKpMeSzMq{u^GjR3FCc~i_hGn|yl(ajt#a0ONq$YJH@+$(4pzZ+ zM@BK!(ZDpuqVS?pW77=Q-~wE1KJ{FtmFu4T;69JtBMq<2&08WEn>L@z6al`Om3}OO z=V2T8IK)t7Yi!g>>T^c?lVAK^LmM1V7ByNV*XNpCcZFWg4lcMkz&6t?N~FqacVHI# z+s-tOSjJgGq7Z#PBg>6hyTGj4C zy6zm=%vvjY|}nT`{3b z-|yQ0an)*G)pR3wQBby_pa%E8>vD~H#~qtPwXk3v3K5HL8`m?!zd1CIEw7z$*o;M$ zc7}iYMLee6g|nVa)FxSor0%A^i7?CltiQ(GP(m8F@6ak-qd*%tCUj@6sjaba%%l}> znl!90H*6in_7<%IEuWbokiDFLp2dJ0mkrO>K2Q^OL;79mz~T zNmsZv$Q1&$Ec7cs5eqt@P6WS;4ppI4GTa5o36l9xIxr)LCSzsfw=*Tlx^G)Ea@!VQ zt*HVKaeU;f3VXMGRjBif|7l7GY)S&4qIiJIn(SnP3NM`u)QPA7vYy=Jx9HvU*?{~_ z-E}*WwI+^kWf-9WH!n`|0!DOufLP&10aOeE2=HPDf*s)GKy9Y&gh{e0z*lc(a`~Z4 zO!V8s7&OzZdq&^sLSz_el2MkP^ju(aa`Q3a?Qo0%8rbW@L6)>#rAt7TaB$xC#bY&L zTVm5@Bj7ofKJ){dSR{ikfOHh5OQ!9JWQYT59F>4NXLgZ)76%-qXdl&ouM}h$X+pRyZW|2#SUgabmP0yu&#cgc%`_lQ0Mbv!loz zMUbcluz2y(iEq$+zd%AD0|cV8gNQo@z%vX6;PBek zU&spynPl*7V|0nQ6yVPS!A z2;Q{K<~RzcKFuTvCr?c%awW~@ZJ}-p+o09nh|Bp&k3WbOva4xGh85^MeyJK7IK!pI zxHd3fCmolt@F!+6D}os5rLB_Tbz(`vcjANloyR#m&zY-6G_$j{JceIcw-*SB*$YQ* z2YzI1Yuh=mr`LV#(@yf-AB@_Fco$jZ`X`_91dRM_Z92T(i%`*)UhauOCB8&E76*p~ zZk?S%l(iq$-~h4#LHW9|3KyZ%5y5IVu^4=xX;p17V_t`Z@}$UbR~SoJqaq?=jkd=O z6xr4+>NS=>%mfWDx6FrP98Vn8+&sjSm@y|kY_rK--pcMBjscSE&vs;n_>yiftkyPq z)RUbaclt7y5vGrrop&I<_O_PqzLk?G+SO_T6`AtPQ)oV9*Bj5GR^X7EO`#ar-aDP7 z6Z?@%YRxC06Y%61uue#kVIhp-Fu-t_7CPss0dXRu!3A;6|=$CBmZBt_MM`F#$ zaR#57>xq zkir%N-Vg$%N&MV9_O*b1y{e%komTrw-;9f0EW>Bkun$V1l`9u7I6CV;svdDme7MQG zysC7Z%NB($wX&#Lxn4GNLqnz1>ZTL&OVtaVo(&Z<&H3?~CEFtVtw;0Dz3WLkh$sFQ zPlV^}f#e1T&5=m9^^wn$!v=TWq<+;m}EI>*L`+NUGKi=1C) ze?$}Jag@u8js4fk5q*3}bz;}OKNr2QFwT zyMD6|ajot-9V1r^=D#=gG!>m{^3{c$ntrnuyel=U)iA~aw-;7c(OPc!FsioPQ!=^> z@4a$MOiKT*(Y2%?YnB=bUi(Bl&H74`e(~KFF40D4erk=QDPMx&M7g-wGtA9 zy=HhQSI>j8l&6y$E?!?#luvNj<~FjZdLNW^ucUU1Z^KZqr6N^pF9Lq*V_iFdEL_ahs}a4~dA(U*Per<|mL$bw z`?0++)q)UU7JWFA-}``_AxC``RF2YUV=P{;b6>d}I*Ziom3|Qa{3QYE36tZiLtF_c z5LLy$n~=HDY?>m^Uf%13=!op8Rfb8Zzn;T7{icW#Xb<0V42@SX| z^qVY6Yvl#AQ!>#>mTKDNW040~jS%@ywBw$6N0(rq*R1@Agfws}cqdmTSox>QMJJ~_HD)EOtFV@kSK)jbrvV>M!|{o( zc+PT7mK}XlxaxOAum$d%TURzF!T8`YA^_nxx)A(<4>?&`h}EvNF&sjugr51D^G%ba zG5$gEz3hF@XGiv>t_rfl6`g@@-Gd&d1I*HrJRdolZ<0%OzwgX599#?%RDhK<2I`nk}Qbc)S{+LP|bIRzbp z5x~{2FTNi_5%MJ>u75AL*t&X&BH=4h+OBnw-4T-6=H+#f-(dXdaf)PjNDs3$Vrz}_ zp0w4k&Ia?NUrNH=SNh6~v<@r27@hPGm;ADxJ+_v?==hz_Bju6A`)HFeQ)@XykC^}D zS&rnOTE2eoHs_FWh~Yqct+C4NxfWAmNbvfKOrP)VO)diJ^B2ncmD_MjmX4JROGm}vtf0DY1MHKPfPG-|E962RiZPfn90 z!f40>Mw#p)jiE`AGi03U)&hoZ0XybOK)x#<`Kp{C{kK~ASO%K1ma;27iDtACxmQJ; zj&GDbfp3>hUr@eDJ$=GV(`n!280JIw-f&>-uY zG|)@jF~3Ul261KbD@$Rzf}Wu=8Ewr%SC+K}mG=&xK&qVv>z+ZK+5pAwsN>!n1iZiz zPQ5tkPujzX0RlOB%vxX0)ZAFX&n?WuU%|o*3gdy7LxKHCCE8Pi26N+_2!cK8gF@=My|09&fR;M-$3ZS77i0(e2GMW>olRRxL z>pLM~GhB2b6mZ??01dAMaUcG_0|4_*qUe9OPGp}j4h1MVE1>tv7~l!`RT$ih8XVxg zk8}?ODq?_2XC#&Qef{fT0=OjnnII7CKBAliNYn&SF7$L1T#zt+DG(C_{9Vvg=cg<% z3Iwe0Hh8PUv5}Rarfb$*)sO0&W;htTPR0@*S_K}BWsALsanySj(2dhw!Bm<_U zzK^IaOC{#JiqJaL;EIG5%TX~`H*SE=+y@#|;T!JsBw9eM3=8Pvl>tutL8`D` z{0{dDD2NAzwf70LYJ zI~8~IK-02`1+hU(D%R|QqE;8l{=#+{tjZazPHSIvhis@sv^6`R=qbb$MEzL(2NK-% A(f|Me diff --git a/models/DICOM/dicom-json-parser.js b/models/DICOM/dicom-json-parser.js index 44168550..2618360a 100644 --- a/models/DICOM/dicom-json-parser.js +++ b/models/DICOM/dicom-json-parser.js @@ -1,4 +1,5 @@ const { DicomUtf8Converter } = require("./dcm4che/DicomUtf8Converter"); +const { JDcm2Json } = require("./dcm4che/dcm2json"); const { dcm2jsonV8 } = require("./dcmtk"); @@ -11,7 +12,7 @@ class DicomJsonParser { async parseFromFilename(filename) { let dicomJson; try { - dicomJson = await dcm2jsonV8.exec(filename); + dicomJson = await JDcm2Json.get(filename); return dicomJson; } catch (e) { @@ -26,7 +27,7 @@ class DicomJsonParser { let dicomUtf8Converter = new DicomUtf8Converter(filename); await dicomUtf8Converter.convert(); - dicomJson = await dcm2jsonV8.exec(filename); + dicomJson = await JDcm2Json.get(filename); return dicomJson; } catch(e) { throw e; From e39eabe357b29c4e4fb9084ebb13f44816d81a8f Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 7 Jan 2024 23:15:38 +0800 Subject: [PATCH 292/365] refactor: remove dcmtk dependencies --- models/DICOM/dcmtk/index.js | 68 +- models/DICOM/dicom-json-parser.js | 3 - package-lock.json | 1944 +---------------------------- package.json | 1 - 4 files changed, 31 insertions(+), 1985 deletions(-) diff --git a/models/DICOM/dcmtk/index.js b/models/DICOM/dcmtk/index.js index c61c8b37..4899aaa1 100644 --- a/models/DICOM/dcmtk/index.js +++ b/models/DICOM/dcmtk/index.js @@ -3,39 +3,8 @@ const fs = require("fs"); const path = require("path"); const _ = require("lodash"); const iconv = require("iconv-lite"); -const dcmtkWinBinaryPath = "models/DICOM/dcmtk/dcmtk-3.6.5-win64-dynamic/bin"; -const dcmtkSupportTransferSyntax = [ - "1.2.840.10008.1.2", - "1.2.840.10008.1.2.1", - "1.2.840.10008.1.2.1.99", - "1.2.840.10008.1.2.2", - "1.2.840.10008.1.2.4.50", - "1.2.840.10008.1.2.4.51", - "1.2.840.10008.1.2.4.53", - "1.2.840.10008.1.2.4.55", - "1.2.840.10008.1.2.4.57", - "1.2.840.10008.1.2.4.70", - "1.2.840.10008.1.2.5" -]; - -const { dcm2json } = require("dicom-to-json"); const dcm2jsonV8 = { - exec: function (dcmfile) { - return new Promise((resolve, reject) => { - try { - dcm2json(dcmfile, function (data) { - data = data.replace(/,\\u0000/g, ""); - data = data.replace(/\\u0000/g, ""); - let obj = JSON.parse(data); - return resolve(obj); - }); - } catch (e) { - console.error(e); - return reject(e); - } - }); - }, dcmString: function (json, tag) { let data = _.get(json, tag); //console.log("d" , data); @@ -71,41 +40,6 @@ async function dcm2jpegCustomCmd(execCmd) { }); } -/** - * - * @param {string} imagesPath - * @param {number} frameNumber - * @param {Array}otherOptions - */ - async function getFrameImage (imagesPath , frameNumber ,otherOptions=[]) { - let jpegFile = imagesPath.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`); - let execCmd = `${dcmtkWinBinaryPath}/dcmj2pnm.exe --write-jpeg "${imagesPath}" "${jpegFile}" --frame ${frameNumber} ${otherOptions.join(" ")}`; - if (process.env.OS == "linux") { - execCmd = `dcmj2pnm --write-jpeg "${imagesPath}" "${jpegFile}" --frame ${frameNumber} ${otherOptions.join(" ")}`; - } - try { - let dcm2jpegStatus = await dcm2jpegCustomCmd(execCmd.trim()); - if (dcm2jpegStatus) { - let rs = fs.createReadStream(jpegFile); - return { - status : true , - imageStream : rs, - imagePath: jpegFile - }; - } - } catch(e) { - console.error(e); - return { - status : false , - imageStream : e, - imagePath: jpegFile - }; - } -} - module.exports = { - dcm2jsonV8: dcm2jsonV8, - dcmtkSupportTransferSyntax: dcmtkSupportTransferSyntax, - dcm2jpegCustomCmd: dcm2jpegCustomCmd, - getFrameImage: getFrameImage + dcm2jsonV8: dcm2jsonV8 }; diff --git a/models/DICOM/dicom-json-parser.js b/models/DICOM/dicom-json-parser.js index 2618360a..9ec6adb2 100644 --- a/models/DICOM/dicom-json-parser.js +++ b/models/DICOM/dicom-json-parser.js @@ -1,8 +1,5 @@ const { DicomUtf8Converter } = require("./dcm4che/DicomUtf8Converter"); const { JDcm2Json } = require("./dcm4che/dcm2json"); -const { - dcm2jsonV8 -} = require("./dcmtk"); const { logger } = require("../../utils/logs/log"); diff --git a/package-lock.json b/package-lock.json index 5248c354..7bd7d64a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dicom-parser": "^1.8.13", - "dicom-to-json": "^1.2.1", "dicomjson-to-fhir": "^1.0.1", "dotenv": "^16.0.3", "env-var": "^7.3.1", @@ -2091,11 +2090,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", - "integrity": "sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A==" - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -2338,26 +2332,6 @@ "node": ">= 10.0.0" } }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", - "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2367,19 +2341,6 @@ "node": ">=8" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", @@ -2513,19 +2474,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/buffer-indexof-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", - "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==" - }, "node_modules/buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -2534,14 +2482,6 @@ "node": ">=4" } }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", - "engines": { - "node": ">=0.2.0" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2578,14 +2518,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", @@ -2630,17 +2562,6 @@ "node": ">=4" } }, - "node_modules/chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", - "dependencies": { - "traverse": ">=0.3.0 <0.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2768,238 +2689,6 @@ "node": ">=10" } }, - "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cmake-js": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-6.3.2.tgz", - "integrity": "sha512-7MfiQ/ijzeE2kO+WFB9bv4QP5Dn2yVaAP2acFJr4NIFy2hT4w6O4EpOTLNcohR5IPX7M4wNf/5taIqMj7UA9ug==", - "dependencies": { - "axios": "^0.21.1", - "bluebird": "^3", - "debug": "^4", - "fs-extra": "^5.0.0", - "is-iojs": "^1.0.1", - "lodash": "^4", - "memory-stream": "0", - "npmlog": "^1.2.0", - "rc": "^1.2.7", - "semver": "^5.0.3", - "splitargs": "0", - "tar": "^4", - "unzipper": "^0.8.13", - "url-join": "0", - "which": "^1.0.9", - "yargs": "^3.6.0" - }, - "bin": { - "cmake-js": "bin/cmake-js" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/cmake-js/node_modules/are-we-there-yet": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.6.tgz", - "integrity": "sha512-Zfw6bteqM9gQXZ1BIWOgM8xEwMrUGoyL8nW13+O+OOgNX3YhuDN1GDgg1NzdTlmm3j+9sHy7uBZ12r+z9lXnZQ==", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.0 || ^1.1.13" - } - }, - "node_modules/cmake-js/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/cmake-js/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/cmake-js/node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dependencies": { - "minipass": "^2.6.0" - } - }, - "node_modules/cmake-js/node_modules/gauge": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", - "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", - "dependencies": { - "ansi": "^0.3.0", - "has-unicode": "^2.0.0", - "lodash.pad": "^4.1.0", - "lodash.padend": "^4.1.0", - "lodash.padstart": "^4.1.0" - } - }, - "node_modules/cmake-js/node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/cmake-js/node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/cmake-js/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/cmake-js/node_modules/npmlog": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-1.2.1.tgz", - "integrity": "sha1-KOe+YZYJtT960d0wChDWTXFiaLY=", - "dependencies": { - "ansi": "~0.3.0", - "are-we-there-yet": "~1.0.0", - "gauge": "~1.2.0" - } - }, - "node_modules/cmake-js/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/cmake-js/node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/cmake-js/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/cmake-js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/cmake-js/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/cmake-js/node_modules/tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/cmake-js/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3642,6 +3331,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3671,17 +3361,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -3694,14 +3373,6 @@ "node": ">=6" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3779,27 +3450,6 @@ "resolved": "https://registry.npmjs.org/dicom-parser/-/dicom-parser-1.8.13.tgz", "integrity": "sha512-8M53FPHS4zM3zvu5fdIWdatqrjpiG2+2M6RJ0IxwqLF4gvCYRsqUIusxYaOiNU0sWaptUpnXeZiXunP0LOIcQw==" }, - "node_modules/dicom-to-json": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/dicom-to-json/-/dicom-to-json-1.2.1.tgz", - "integrity": "sha512-1AbKqqQUrJbbRpGy6BKw7wldPyL6fHZsnaRVtBB3VPhnJ26cTPZ+rGFBGiDmKPVlgAklCftmclXC3boMAnoBRA==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.5.0", - "cmake-js": "^6.3.0", - "node-addon-api": "^4.3.0", - "prebuild-install": "^6.1.4", - "run-script-os": "^1.1.6" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/dicom-to-json/node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - }, "node_modules/dicomjson-to-fhir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dicomjson-to-fhir/-/dicomjson-to-fhir-1.0.1.tgz", @@ -3914,41 +3564,6 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/duplexer2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4245,14 +3860,6 @@ "node": ">= 0.6" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "engines": { - "node": ">=6" - } - }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -4435,11 +4042,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4632,16 +4234,6 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "node_modules/fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -4672,42 +4264,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dependencies": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/fstream/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/fstream/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4961,11 +4517,6 @@ "ini": "^1.3.2" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" - }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -5250,15 +4801,8 @@ "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "engines": { - "node": ">=0.10.0" - } + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "node_modules/ip": { "version": "2.0.0", @@ -5331,11 +4875,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-iojs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-iojs/-/is-iojs-1.1.0.tgz", - "integrity": "sha1-TBEDO11dlNbqs3dd7cm+fQCDJfE=" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5736,17 +5275,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5767,11 +5295,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "node_modules/listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" - }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -5864,21 +5387,6 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, - "node_modules/lodash.pad": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", - "integrity": "sha1-QzCUmoM6fI2iLMIPaibE1Z3runA=" - }, - "node_modules/lodash.padend": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" - }, - "node_modules/lodash.padstart": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", - "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" - }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -5991,35 +5499,6 @@ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, - "node_modules/memory-stream": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/memory-stream/-/memory-stream-0.0.3.tgz", - "integrity": "sha1-6+jdHDuLw4wOeUHp3dWuvmtN6D8=", - "dependencies": { - "readable-stream": "~1.0.26-2" - } - }, - "node_modules/memory-stream/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/memory-stream/node_modules/readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/memory-stream/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -6195,17 +5674,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6234,7 +5702,8 @@ "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "node_modules/minimist-options": { "version": "4.1.0", @@ -6293,11 +5762,6 @@ "node": ">=10" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, "node_modules/mocha": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", @@ -6979,11 +6443,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -7017,22 +6476,6 @@ "node": ">=12.22.0" } }, - "node_modules/node-abi": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", - "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", - "dependencies": { - "semver": "^5.4.1" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", @@ -7118,14 +6561,6 @@ "set-blocking": "^2.0.0" } }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7194,17 +6629,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -7641,153 +7065,6 @@ "node": ">=0.10.0" } }, - "node_modules/prebuild-install": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", - "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", - "dependencies": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.21.0", - "npmlog": "^4.0.1", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^3.0.3", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/prebuild-install/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prebuild-install/node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "node_modules/prebuild-install/node_modules/are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/prebuild-install/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/prebuild-install/node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/prebuild-install/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prebuild-install/node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "node_modules/prebuild-install/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/prebuild-install/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/prebuild-install/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/prebuild-install/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prebuild-install/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7820,15 +7097,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7920,20 +7188,6 @@ "node": ">=0.10.0" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -8435,11 +7689,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -8531,35 +7780,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-get": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", - "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", - "dependencies": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -8665,11 +7885,6 @@ "readable-stream": "^3.0.0" } }, - "node_modules/splitargs": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/splitargs/-/splitargs-0.0.7.tgz", - "integrity": "sha1-/p965lc3GzOxDLgNoUPPgknPazs=" - }, "node_modules/standard-version": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", @@ -8934,14 +8149,6 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", @@ -9050,22 +8257,6 @@ "node": ">= 10" } }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -9152,14 +8343,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, - "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", - "engines": { - "node": "*" - } - }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -9175,17 +8358,6 @@ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "devOptional": true }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -9295,51 +8467,6 @@ "node": ">= 0.8" } }, - "node_modules/unzipper": { - "version": "0.8.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.8.14.tgz", - "integrity": "sha512-8rFtE7EP5ssOwGpN2dt1Q4njl0N1hUXJ7sSPz0leU2hRdq6+pra57z4YPBlVqm40vcgv6ooKZEAx48fMTv9x4w==", - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "~1.0.10", - "listenercount": "~1.0.1", - "readable-stream": "~2.1.5", - "setimmediate": "~1.0.4" - } - }, - "node_modules/unzipper/node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" - }, - "node_modules/unzipper/node_modules/process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "node_modules/unzipper/node_modules/readable-stream": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", - "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", - "dependencies": { - "buffer-shims": "^1.0.0", - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/unzipper/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -9350,11 +8477,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-join": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", - "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9427,17 +8549,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -9446,17 +8557,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=", - "bin": { - "window-size": "cli.js" - }, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -9487,18 +8587,6 @@ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -9516,49 +8604,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -9592,11 +8637,6 @@ "node": ">=0.4" } }, - "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -9611,20 +8651,6 @@ "node": ">= 6" } }, - "node_modules/yargs": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", - "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", - "dependencies": { - "camelcase": "^2.0.1", - "cliui": "^3.0.3", - "decamelize": "^1.1.1", - "os-locale": "^1.4.0", - "string-width": "^1.0.1", - "window-size": "^0.1.4", - "y18n": "^3.2.0" - } - }, "node_modules/yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", @@ -9673,49 +8699,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -11268,11 +10251,6 @@ "uri-js": "^4.2.2" } }, - "ansi": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", - "integrity": "sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A==" - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -11469,39 +10447,12 @@ "node-addon-api": "^3.1.0" } }, - "big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" - }, - "binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", - "requires": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", @@ -11607,26 +10558,11 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "buffer-indexof-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", - "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==" - }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==" - }, "buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, - "buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==" - }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -11654,11 +10590,6 @@ "dev": true, "peer": true }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==" - }, "camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", @@ -11693,14 +10624,6 @@ "type-detect": "^4.0.5" } }, - "chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", - "requires": { - "traverse": ">=0.3.0 <0.4" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -11797,216 +10720,6 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "cmake-js": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-6.3.2.tgz", - "integrity": "sha512-7MfiQ/ijzeE2kO+WFB9bv4QP5Dn2yVaAP2acFJr4NIFy2hT4w6O4EpOTLNcohR5IPX7M4wNf/5taIqMj7UA9ug==", - "requires": { - "axios": "^0.21.1", - "bluebird": "^3", - "debug": "^4", - "fs-extra": "^5.0.0", - "is-iojs": "^1.0.1", - "lodash": "^4", - "memory-stream": "0", - "npmlog": "^1.2.0", - "rc": "^1.2.7", - "semver": "^5.0.3", - "splitargs": "0", - "tar": "^4", - "unzipper": "^0.8.13", - "url-join": "0", - "which": "^1.0.9", - "yargs": "^3.6.0" - }, - "dependencies": { - "are-we-there-yet": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.6.tgz", - "integrity": "sha512-Zfw6bteqM9gQXZ1BIWOgM8xEwMrUGoyL8nW13+O+OOgNX3YhuDN1GDgg1NzdTlmm3j+9sHy7uBZ12r+z9lXnZQ==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.0 || ^1.1.13" - } - }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "requires": { - "minipass": "^2.6.0" - } - }, - "gauge": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", - "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", - "requires": { - "ansi": "^0.3.0", - "has-unicode": "^2.0.0", - "lodash.pad": "^4.1.0", - "lodash.padend": "^4.1.0", - "lodash.padstart": "^4.1.0" - } - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "npmlog": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-1.2.1.tgz", - "integrity": "sha1-KOe+YZYJtT960d0wChDWTXFiaLY=", - "requires": { - "ansi": "~0.3.0", - "are-we-there-yet": "~1.0.0", - "gauge": "~1.2.0" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "requires": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, "color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -12498,7 +11211,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decamelize-keys": { "version": "1.1.1", @@ -12518,14 +11232,6 @@ } } }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" - } - }, "deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -12535,11 +11241,6 @@ "type-detect": "^4.0.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -12598,25 +11299,6 @@ "resolved": "https://registry.npmjs.org/dicom-parser/-/dicom-parser-1.8.13.tgz", "integrity": "sha512-8M53FPHS4zM3zvu5fdIWdatqrjpiG2+2M6RJ0IxwqLF4gvCYRsqUIusxYaOiNU0sWaptUpnXeZiXunP0LOIcQw==" }, - "dicom-to-json": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/dicom-to-json/-/dicom-to-json-1.2.1.tgz", - "integrity": "sha512-1AbKqqQUrJbbRpGy6BKw7wldPyL6fHZsnaRVtBB3VPhnJ26cTPZ+rGFBGiDmKPVlgAklCftmclXC3boMAnoBRA==", - "requires": { - "bindings": "^1.5.0", - "cmake-js": "^6.3.0", - "node-addon-api": "^4.3.0", - "prebuild-install": "^6.1.4", - "run-script-os": "^1.1.6" - }, - "dependencies": { - "node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - } - } - }, "dicomjson-to-fhir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dicomjson-to-fhir/-/dicomjson-to-fhir-1.0.1.tgz", @@ -12680,69 +11362,32 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - } - } - }, - "dottie": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", - "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "p-limit": "^2.0.0" } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true } } }, + "dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -12968,11 +11613,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - }, "express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -13127,11 +11767,6 @@ "flat-cache": "^3.0.4" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -13269,16 +11904,6 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -13299,35 +11924,6 @@ "dev": true, "optional": true }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -13531,11 +12127,6 @@ "ini": "^1.3.2" } }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" - }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -13732,12 +12323,8 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "ip": { "version": "2.0.0", @@ -13792,11 +12379,6 @@ "is-extglob": "^2.1.1" } }, - "is-iojs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-iojs/-/is-iojs-1.1.0.tgz", - "integrity": "sha1-TBEDO11dlNbqs3dd7cm+fQCDJfE=" - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -14077,14 +12659,6 @@ } } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -14102,11 +12676,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" - }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -14192,21 +12761,6 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, - "lodash.pad": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", - "integrity": "sha1-QzCUmoM6fI2iLMIPaibE1Z3runA=" - }, - "lodash.padend": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" - }, - "lodash.padstart": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", - "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" - }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -14288,37 +12842,6 @@ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, - "memory-stream": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/memory-stream/-/memory-stream-0.0.3.tgz", - "integrity": "sha1-6+jdHDuLw4wOeUHp3dWuvmtN6D8=", - "requires": { - "readable-stream": "~1.0.26-2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, "meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -14449,11 +12972,6 @@ "mime-db": "1.52.0" } }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -14476,7 +12994,8 @@ "minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "minimist-options": { "version": "4.1.0", @@ -14519,11 +13038,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, "mocha": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", @@ -15001,11 +13515,6 @@ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, - "napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -15033,21 +13542,6 @@ "debug": "^4.3.4" } }, - "node-abi": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", - "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", - "requires": { - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" - } - } - }, "node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", @@ -15107,11 +13601,6 @@ "set-blocking": "^2.0.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -15165,14 +13654,6 @@ "word-wrap": "^1.2.3" } }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -15496,131 +13977,6 @@ "xtend": "^4.0.0" } }, - "prebuild-install": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", - "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", - "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.21.0", - "npmlog": "^4.0.1", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^3.0.3", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15647,15 +14003,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -15721,17 +14068,6 @@ } } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -16098,11 +14434,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -16175,21 +14506,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", - "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", - "requires": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -16281,11 +14597,6 @@ "readable-stream": "^3.0.0" } }, - "splitargs": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/splitargs/-/splitargs-0.0.7.tgz", - "integrity": "sha1-/p965lc3GzOxDLgNoUPPgknPazs=" - }, "standard-version": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", @@ -16482,11 +14793,6 @@ "min-indent": "^1.0.0" } }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, "strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", @@ -16567,24 +14873,6 @@ "yallist": "^4.0.0" } }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - } - } - }, "tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -16661,11 +14949,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, - "traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" - }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -16678,14 +14961,6 @@ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "devOptional": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -16762,53 +15037,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "unzipper": { - "version": "0.8.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.8.14.tgz", - "integrity": "sha512-8rFtE7EP5ssOwGpN2dt1Q4njl0N1hUXJ7sSPz0leU2hRdq6+pra57z4YPBlVqm40vcgv6ooKZEAx48fMTv9x4w==", - "requires": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "~1.0.10", - "listenercount": "~1.0.1", - "readable-stream": "~2.1.5", - "setimmediate": "~1.0.4" - }, - "dependencies": { - "bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "readable-stream": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", - "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", - "requires": { - "buffer-shims": "^1.0.0", - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -16819,11 +15047,6 @@ "punycode": "^2.1.0" } }, - "url-join": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", - "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16880,14 +15103,6 @@ "webidl-conversions": "^3.0.0" } }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -16896,11 +15111,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" - }, "wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -16928,48 +15138,6 @@ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "wrap-ansi-cjs": { "version": "npm:wrap-ansi@7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -16996,11 +15164,6 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, - "y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -17012,53 +15175,6 @@ "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", "dev": true }, - "yargs": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", - "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", - "requires": { - "camelcase": "^2.0.1", - "cliui": "^3.0.3", - "decamelize": "^1.1.1", - "os-locale": "^1.4.0", - "string-width": "^1.0.1", - "window-size": "^0.1.4", - "y18n": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", diff --git a/package.json b/package.json index f9bc154b..de327afd 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dicom-parser": "^1.8.13", - "dicom-to-json": "^1.2.1", "dicomjson-to-fhir": "^1.0.1", "dotenv": "^16.0.3", "env-var": "^7.3.1", From 5a0388e97aa43f0901fe5b1e7784c0a24cb530be Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 8 Jan 2024 13:29:56 +0800 Subject: [PATCH 293/365] refactor: error handling in StoreInstanceController - Replace manual error handling with ApiErrorArrayHandler for improved error management and readability --- api/dicom-web/controller/STOW-RS/storeInstance.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/api/dicom-web/controller/STOW-RS/storeInstance.js b/api/dicom-web/controller/STOW-RS/storeInstance.js index 9a47cc7c..2cbbf5bd 100644 --- a/api/dicom-web/controller/STOW-RS/storeInstance.js +++ b/api/dicom-web/controller/STOW-RS/storeInstance.js @@ -4,6 +4,7 @@ const { ApiLogger } = require("../../../../utils/logs/api-logger"); const { Controller } = require("../../../controller.class"); const { StowRsRequestMultipartParser } = require("./service/request-multipart-parser"); const { StowRsService } = require("./service/stow-rs.service"); +const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class StoreInstanceController extends Controller { constructor(req, res) { @@ -38,15 +39,8 @@ class StoreInstanceController extends Controller { return this.response.end(JSON.stringify(storeMessage)); } catch (e) { - let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); - apiLogger.logger.error(errorStr); - - let errorMessage = - errorResponseMessage.getInternalServerErrorMessage(errorStr); - this.response.writeHead(500, { - "Content-Type": "application/dicom+json" - }); - return this.response.end(JSON.stringify(errorMessage)); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + return apiErrorArrayHandler.doErrorResponse(); } } } From 3217d28682c0dff108e79855a88b367e94f6e44e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 8 Jan 2024 14:24:06 +0800 Subject: [PATCH 294/365] refactor: remove `otherFields` of `getInstanceFrameObj` - Remove the 'otherFields' parameter from the getInstanceFrameObj function - Updated calls to this function to remove the 'otherFields' argument accordingly. --- .../dicom-web/controller/WADO-RS/service/rendered.service.js | 2 +- api/WADO-URI/service/WADO-URI.service.js | 5 +---- api/dicom-web/controller/WADO-RS/service/rendered.service.js | 5 +++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js index 2a303f7f..6ec59e04 100644 --- a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -197,7 +197,7 @@ This instance NumberOfFrames is : ${this.dicomNumberOfFrames} , But request ${JS * @param {Object} iParam * @return { Promise | Promise } */ -async function getInstanceFrameObj(iParam, otherFields = {}) { +async function getInstanceFrameObj(iParam) { let { studyUID, seriesUID, instanceUID } = iParam; try { /** @type { import("sequelize").FindOptions } */ diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 5bf7a2d3..35fcfaa8 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -89,10 +89,7 @@ class WadoUriService { async handleFrameNumberAndGetImageObj() { let imagePathObj = await this.getDicomInstancePathObj(); - let instanceFramesObj = await renderedService.getInstanceFrameObj(imagePathObj, { - "00281050": 1, - "00281051": 1 - }); + let instanceFramesObj = await renderedService.getInstanceFrameObj(imagePathObj); let instanceTotalFrameNumber = _.get(instanceFramesObj, "00280008.Value.0", 1); let windowCenter = _.get(instanceFramesObj, "00281050.Value.0", ""); let windowWidth = _.get(instanceFramesObj, "00281051.Value.0", ""); diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index ec86feab..b1021253 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -270,7 +270,7 @@ This instance NumberOfFrames is : ${this.dicomNumberOfFrames} , But request ${JS * @param {Object} iParam * @return { Promise | Promise } */ -async function getInstanceFrameObj(iParam, otherFields={}) { +async function getInstanceFrameObj(iParam) { let { studyUID, seriesUID, instanceUID } = iParam; try { /** @type { import("mongoose").FilterQuery } */ @@ -304,7 +304,8 @@ async function getInstanceFrameObj(iParam, otherFields={}) { instancePath: 1, "00280008": 1, //number of frames "00020010": 1, //transfer syntax UID - ...otherFields + "00281050": 1, // Window Center + "00281051": 1 // Window Width }).exec(); if (doc) { let docObj = doc.toObject(); From 7e275f24453134b04b2588c3a86c148037fb7497 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 8 Jan 2024 14:52:14 +0800 Subject: [PATCH 295/365] docs: correct `InstanceFrameObj` type --- utils/typeDef/WADO-RS/WADO-RS.def.js | 6 ++++-- utils/typeDef/dicom.js | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/utils/typeDef/WADO-RS/WADO-RS.def.js b/utils/typeDef/WADO-RS/WADO-RS.def.js index c8f897b1..36d68090 100644 --- a/utils/typeDef/WADO-RS/WADO-RS.def.js +++ b/utils/typeDef/WADO-RS/WADO-RS.def.js @@ -12,8 +12,10 @@ * seriesUID: string, * instanceUID: string, * instancePath: string, - * "00280008": string, - * "00020010": string + * "00280008": import("../dicom").DicomJsonItem, + * "00020010": import("../dicom").DicomJsonItem, + * "00281050": import("../dicom").DicomJsonItem, + * "00281051": import("../dicom").DicomJsonItem * } } InstanceFrameObj */ diff --git a/utils/typeDef/dicom.js b/utils/typeDef/dicom.js index 9e6c32cf..afe71752 100644 --- a/utils/typeDef/dicom.js +++ b/utils/typeDef/dicom.js @@ -26,6 +26,12 @@ * @property {boolean} [isRecycle] */ +/** + * @typedef DicomJsonItem + * @property {string} vr + * @property {any[]} Value + */ + const DICOM = true; module.exports.unUse = {}; From a389efa1f82e5736afc0e90c52194e72019d6043 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 8 Jan 2024 15:32:11 +0800 Subject: [PATCH 296/365] refactor: move `getInstanceFrameObj` to `InstanceModel` - This function belongs to InstanceModel and is more reasonable. --- .../WADO-RS/service/rendered.service.js | 65 ++----------------- models/mongodb/models/instance.model.js | 62 +++++++++++++++++- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index b1021253..011657b7 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -1,5 +1,6 @@ const path = require("path"); const mongoose = require("mongoose"); +const { InstanceModel } = require("@dbModels/instance.model"); const fs = require("fs"); const sharp = require("sharp"); const _ = require("lodash"); @@ -155,7 +156,7 @@ class FramesWriter { async write() { let multipartWriter = new MultipartWriter([], this.request, this.response); for(let imagePathObj of this.imagePaths) { - let instanceFramesObj = await getInstanceFrameObj(imagePathObj); + let instanceFramesObj = await InstanceModel.getInstanceFrame(imagePathObj); if(_.isUndefined(instanceFramesObj)) continue; let dicomNumberOfFrames = _.get(instanceFramesObj, "00280008.Value.0", 1); dicomNumberOfFrames = parseInt(dicomNumberOfFrames); @@ -188,7 +189,7 @@ class InstanceFramesWriter extends FramesWriter { async write() { let multipartWriter = new MultipartWriter([], this.request, this.response); - let instanceFramesObj = await getInstanceFrameObj(this.imagePaths[0]); + let instanceFramesObj = await InstanceModel.getInstanceFrame(this.imagePaths[0]); if (_.isUndefined(instanceFramesObj)) { return this.response.status(400).json( errorResponse.getBadRequestErrorMessage(`instance: ${this.request.params.instanceUID} doesn't have pixel data`) @@ -211,7 +212,7 @@ class InstanceFramesListWriter extends FramesWriter { async write() { let {frameNumber} = this.request.params; - this.instanceFramesObj = await getInstanceFrameObj(this.imagePaths[0]); + this.instanceFramesObj = await InstanceModel.getInstanceFrame(this.imagePaths[0]); if (_.isUndefined(this.instanceFramesObj)) { return this.response.status(400).json( errorResponse.getBadRequestErrorMessage(`instance: ${this.request.params.instanceUID} doesn't have pixel data`) @@ -265,62 +266,6 @@ This instance NumberOfFrames is : ${this.dicomNumberOfFrames} , But request ${JS } } -/** - * - * @param {Object} iParam - * @return { Promise | Promise } - */ -async function getInstanceFrameObj(iParam) { - let { studyUID, seriesUID, instanceUID } = iParam; - try { - /** @type { import("mongoose").FilterQuery } */ - let query = { - $and: [ - { - studyUID: studyUID - }, - { - seriesUID: seriesUID - }, - { - instanceUID: instanceUID - }, - { - "00080016.Value": { - $nin: notImageSOPClass - } - }, - { - deleteStatus: { - $eq: 0 - } - } - ] - }; - let doc = await mongoose.model("dicom").findOne(query, { - studyUID: 1, - seriesUID: 1, - instanceUID: 1, - instancePath: 1, - "00280008": 1, //number of frames - "00020010": 1, //transfer syntax UID - "00281050": 1, // Window Center - "00281051": 1 // Window Width - }).exec(); - if (doc) { - let docObj = doc.toObject(); - docObj.instancePath = path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - docObj.instancePath - ); - return docObj; - } - return undefined; - } catch (e) { - throw e; - } -} - /** * * @param {import("http").IncomingMessage} req @@ -411,7 +356,7 @@ async function writeRenderedImages(req, dicomNumberOfFrames, instanceFramesObj, module.exports.handleImageQuality = handleImageQuality; module.exports.handleImageICCProfile = handleImageICCProfile; module.exports.handleViewport = handleViewport; -module.exports.getInstanceFrameObj = getInstanceFrameObj; +module.exports.getInstanceFrameObj = InstanceModel.getInstanceFrame; module.exports.postProcessFrameImage = postProcessFrameImage; module.exports.writeRenderedImages = writeRenderedImages; module.exports.RenderedImageMultipartWriter = RenderedImageMultipartWriter; diff --git a/models/mongodb/models/instance.model.js b/models/mongodb/models/instance.model.js index ce59ca29..49635e2d 100644 --- a/models/mongodb/models/instance.model.js +++ b/models/mongodb/models/instance.model.js @@ -13,6 +13,7 @@ const { logger } = require("../../../utils/logs/log"); const { raccoonConfig } = require("@root/config-class"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { DicomSchemaOptionsFactory, InstanceDocDicomJsonHandler } = require("../schema/dicom.schema"); +const notImageSOPClass = require("@models/DICOM/dicomWEB/notImageSOPClass"); let verifyingObserverSchema = new mongoose.Schema( @@ -149,7 +150,7 @@ let dicomSchemaOptions = _.merge( } ] }); - + return await mongoose.model("dicom").findOne(query, { studyUID: 1, seriesUID: 1, @@ -163,6 +164,65 @@ let dicomSchemaOptions = _.merge( .skip(instanceCountOfStudy >> 1) .limit(1) .exec(); + }, + /** + * + * @param {object} iParam + * @param {string} iParam.studyUID + * @param {string} iParam.seriesUID + * @param {string} iParam.instanceUID + * @returns { Promise | Promise } + */ + getInstanceFrame: async function (iParam) { + let { studyUID, seriesUID, instanceUID } = iParam; + + try { + /** @type { import("mongoose").FilterQuery } */ + let query = { + $and: [ + { + studyUID: studyUID + }, + { + seriesUID: seriesUID + }, + { + instanceUID: instanceUID + }, + { + "00080016.Value": { + $nin: notImageSOPClass + } + }, + { + deleteStatus: { + $eq: 0 + } + } + ] + }; + let doc = await mongoose.model("dicom").findOne(query, { + studyUID: 1, + seriesUID: 1, + instanceUID: 1, + instancePath: 1, + "00280008": 1, //number of frames + "00020010": 1, //transfer syntax UID + "00281050": 1, // Window Center + "00281051": 1 // Window Width + }).exec(); + if (doc) { + let docObj = doc.toObject(); + docObj.instancePath = path.join( + raccoonConfig.dicomWebConfig.storeRootPath, + docObj.instancePath + ); + return docObj; + } + return undefined; + } catch (e) { + throw e; + } } } } From e2a15eaf6a8d501b36bcc6e4a8fc63d52bc9c817 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 8 Jan 2024 18:53:34 +0800 Subject: [PATCH 297/365] refactor: encapsulate the process of handle parameters fns into class --- api/WADO-URI/service/WADO-URI.service.js | 14 +- .../WADO-RS/service/rendered.service.js | 205 +++++++++--------- 2 files changed, 108 insertions(+), 111 deletions(-) diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 35fcfaa8..7e3e0ad7 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -1,7 +1,7 @@ const mongoose = require("mongoose"); const fs = require("fs"); const _ = require("lodash"); -const renderedService = require("../../dicom-web/controller/WADO-RS/service/rendered.service"); +const { RenderedImageProcessParameterHandler, getInstanceFrameObj } = require("../../dicom-web/controller/WADO-RS/service/rendered.service"); const { Dcm2JpgExecutor } = require("../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); const sharp = require('sharp'); @@ -26,6 +26,10 @@ class WadoUriService { this.response = res; this.apiLogger = apiLogger; this.auditBeginTransferring(); + + let clonedRequestQuery = _.cloneDeep(this.request?.query); + _.set(clonedRequestQuery, "imageQuality", _.get(this.request.query, "imageQuality", "")); + this.renderedImageProcessParameterHandler = new RenderedImageProcessParameterHandler(clonedRequestQuery); } /** @@ -89,7 +93,7 @@ class WadoUriService { async handleFrameNumberAndGetImageObj() { let imagePathObj = await this.getDicomInstancePathObj(); - let instanceFramesObj = await renderedService.getInstanceFrameObj(imagePathObj); + let instanceFramesObj = await getInstanceFrameObj(imagePathObj); let instanceTotalFrameNumber = _.get(instanceFramesObj, "00280008.Value.0", 1); let windowCenter = _.get(instanceFramesObj, "00281050.Value.0", ""); let windowWidth = _.get(instanceFramesObj, "00281051.Value.0", ""); @@ -131,9 +135,7 @@ class WadoUriService { } async handleImageQuality(magick) { - renderedService.handleImageQuality({ - quality: _.get(this.request.query, "imageQuality", "") - }, magick); + this.renderedImageProcessParameterHandler.handleImageQuality(magick); } async handleRegion(param, imageSharp, magick) { @@ -164,7 +166,7 @@ class WadoUriService { } async handleImageICCProfile(magick) { - await renderedService.handleImageICCProfile(this.request.query, magick, this.request.query.objectUID); + await this.renderedImageProcessParameterHandler.handleImageICCProfile(magick, this.request.query.objectUID); } async auditBeginTransferring() { diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index 011657b7..ae0eae78 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -14,91 +14,97 @@ const { logger } = require("../../../../../utils/logs/log"); const { raccoonConfig } = require("../../../../../config-class"); -/** - * - * @param {*} param The req.query - * @param {Magick} magick - */ - function handleImageQuality(param, magick) { - if (param.quality) { - magick.quality(param.quality); +class RenderedImageProcessParameterHandler { + #params; + constructor(params) { + this.#params = params; } -} -/** - * - * @param {*} param The req.query - * @param {Magick} magick - * @param {string} instanceID - */ - async function handleImageICCProfile(param, magick, instanceID) { - let iccProfileAction = { - "no" : async ()=> {}, - "yes": async ()=> { - let iccProfileBinaryFile = await mongoose.model("dicomBulkData").findOne({ - $and: [ - { - binaryValuePath: "00480105.Value.0.00282000.InlineBinary" - }, - { - instanceUID: instanceID - } - ] - }); - if(!iccProfileBinaryFile) throw new Error("The Image dose not have icc profile tag"); - let iccProfileSrc = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename); - let dest = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename + `.icc`); - if (!fs.existsSync(dest)) fs.copyFileSync(iccProfileSrc, dest); - magick.iccProfile(dest); - }, - "srgb": async ()=> { - magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/sRGB.icc")); - }, - "adobergb": async () => { - magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/adobeRGB.icc")); - }, - "rommrgb": async ()=> { - magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/rommRGB.icc")); - } - }; - try { - if (param.iccprofile) { - await iccProfileAction[param.iccprofile](); + /** + * + * @param {*} param The req.query + * @param {Magick} magick + */ + handleImageQuality(magick) { + if (this.#params?.quality) { + magick.quality(this.#params.quality); } - } catch(e) { - console.error("set icc profile error:" , e); - throw e; } -} -/** - * - * @param {*} param - * @param {sharp.Sharp} imageSharp - * @param {Magick} magick - */ - async function handleViewport(param, imageSharp, magick) { - if (param.viewport) { - let imageMetadata = await imageSharp.metadata(); - let viewportSplit = param.viewport.split(",").map(v => Number(v)); - if (viewportSplit.length == 2) { - let [vw, vh] = viewportSplit; - magick.resize(vw, vh); - } else { - let [vw, vh, sx, sy, sw, sh] = viewportSplit; - magick.resize(vw, vh); - if (sw == 0) sw = imageMetadata.width - sx; - if (sh == 0) sh = imageMetadata.height - sy; - - if (sw < 0) { - magick.flip(); - sw = Math.abs(sw); + /** + * + * @param {Magick} magick + * @param {string} instanceID + */ + async handleImageICCProfile(magick, instanceID) { + let iccProfileAction = { + "no": async () => { }, + "yes": async () => { + let iccProfileBinaryFile = await mongoose.model("dicomBulkData").findOne({ + $and: [ + { + binaryValuePath: "00480105.Value.0.00282000.InlineBinary" + }, + { + instanceUID: instanceID + } + ] + }); + if (!iccProfileBinaryFile) throw new Error("The Image dose not have icc profile tag"); + let iccProfileSrc = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename); + let dest = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename + `.icc`); + if (!fs.existsSync(dest)) fs.copyFileSync(iccProfileSrc, dest); + magick.iccProfile(dest); + }, + "srgb": async () => { + magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/sRGB.icc")); + }, + "adobergb": async () => { + magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/adobeRGB.icc")); + }, + "rommrgb": async () => { + magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/rommRGB.icc")); + } + }; + try { + if (this.#params?.iccprofile) { + await iccProfileAction[this.#params.iccprofile](); } - if (sh < 0) { - magick.flop(); - sh = Math.abs(sw); + } catch (e) { + console.error("set icc profile error:", e); + throw e; + } + } + + + /** + * + * @param {sharp.Sharp} imageSharp + * @param {Magick} magick + */ + async handleViewport(imageSharp, magick) { + if (this.#params?.viewport) { + let imageMetadata = await imageSharp.metadata(); + let viewportSplit = this.#params.viewport.split(",").map(v => Number(v)); + if (viewportSplit.length == 2) { + let [vw, vh] = viewportSplit; + magick.resize(vw, vh); + } else { + let [vw, vh, sx, sy, sw, sh] = viewportSplit; + magick.resize(vw, vh); + if (sw == 0) sw = imageMetadata.width - sx; + if (sh == 0) sh = imageMetadata.height - sy; + + if (sw < 0) { + magick.flip(); + sw = Math.abs(sw); + } + if (sh < 0) { + magick.flop(); + sh = Math.abs(sw); + } + magick.crop(sx, sy, sw, sh); } - magick.crop(sx, sy, sw, sh); } } } @@ -155,9 +161,9 @@ class FramesWriter { async write() { let multipartWriter = new MultipartWriter([], this.request, this.response); - for(let imagePathObj of this.imagePaths) { + for (let imagePathObj of this.imagePaths) { let instanceFramesObj = await InstanceModel.getInstanceFrame(imagePathObj); - if(_.isUndefined(instanceFramesObj)) continue; + if (_.isUndefined(instanceFramesObj)) continue; let dicomNumberOfFrames = _.get(instanceFramesObj, "00280008.Value.0", 1); dicomNumberOfFrames = parseInt(dicomNumberOfFrames); await writeRenderedImages(this.request, dicomNumberOfFrames, instanceFramesObj, multipartWriter); @@ -210,7 +216,7 @@ class InstanceFramesListWriter extends FramesWriter { } async write() { - let {frameNumber} = this.request.params; + let { frameNumber } = this.request.params; this.instanceFramesObj = await InstanceModel.getInstanceFrame(this.imagePaths[0]); if (_.isUndefined(this.instanceFramesObj)) { @@ -234,7 +240,7 @@ class InstanceFramesListWriter extends FramesWriter { } isInvalidFrameNumber() { - for(let i = 0; i < this.request.params.frameNumber.length ; i++) { + for (let i = 0; i < this.request.params.frameNumber.length; i++) { let frame = this.request.params.frameNumber[i]; if (frame > this.dicomNumberOfFrames) { let badRequestMessage = errorResponse.getBadRequestErrorMessage(`Bad frame number , \ @@ -259,7 +265,7 @@ This instance NumberOfFrames is : ${this.dicomNumberOfFrames} , But request ${JS this.response.writeHead(200, { "Content-Type": "image/jpeg" }); - + return postProcessResult.magick.toBuffer(); } throw new Error(`Can not process this image, instanceUID: ${this.instanceFramesObj.instanceUID}, frameNumber: ${this.request.frameNumber[0]}`); @@ -276,7 +282,7 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { try { let dicomFilename = instanceFramesObj.instancePath; - let jpegFile = dicomFilename.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`); + let jpegFile = dicomFilename.replace(/\.dcm\b/gi, `.${frameNumber - 1}.jpg`); let dcm2jpgOptions = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync(); dcm2jpgOptions.frameNumber = frameNumber; @@ -289,20 +295,11 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { if (getFrameImageStatus.status) { let imageSharp = sharp(jpegFile); let magick = new Magick(jpegFile); - handleImageQuality( - req.query, - magick - ); - await handleImageICCProfile( - req.query, - magick, - instanceFramesObj.instanceUID - ); - await handleViewport( - req.query, - imageSharp, - magick - ); + + let renderedImageProcessParameterHandler = new RenderedImageProcessParameterHandler(req.query); + renderedImageProcessParameterHandler.handleImageQuality(magick); + await renderedImageProcessParameterHandler.handleImageICCProfile(magick, instanceFramesObj.instanceUID); + await renderedImageProcessParameterHandler.handleViewport(imageSharp, magick); await magick.execCommand(); return { status: true, @@ -315,7 +312,7 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { message: "get frame image failed", magick: undefined }; - } catch(e) { + } catch (e) { console.error(e); return { status: false, @@ -338,24 +335,22 @@ async function writeRenderedImages(req, dicomNumberOfFrames, instanceFramesObj, let frames = dicomNumberOfFrames; if (!Array.isArray(frames)) frames = [...Array(frames).keys()].map(i => i + 1); - for (let i = 0 ; i < frames.length; i++) { + for (let i = 0; i < frames.length; i++) { let frameNumber = frames[i]; let postProcessResult = await postProcessFrameImage(req, frameNumber, instanceFramesObj); let buffer = postProcessResult.magick.toBuffer(); multipartWriter.writeBuffer(buffer, { "Content-Type": "image/jpeg", - "Content-Location": `/dicom-web/studies/${instanceFramesObj.studyUID}/series/${instanceFramesObj.seriesUID}/instances/${instanceFramesObj.instanceUID}/frames/${i+1}/rendered` + "Content-Location": `/dicom-web/studies/${instanceFramesObj.studyUID}/series/${instanceFramesObj.seriesUID}/instances/${instanceFramesObj.instanceUID}/frames/${i + 1}/rendered` }); } - } catch(e) { + } catch (e) { console.error(e); throw e; } } -module.exports.handleImageQuality = handleImageQuality; -module.exports.handleImageICCProfile = handleImageICCProfile; -module.exports.handleViewport = handleViewport; +module.exports.RenderedImageProcessParameterHandler = RenderedImageProcessParameterHandler; module.exports.getInstanceFrameObj = InstanceModel.getInstanceFrame; module.exports.postProcessFrameImage = postProcessFrameImage; module.exports.writeRenderedImages = writeRenderedImages; From afe65d4e7f282a7dde06bac4d831c670a7d25918 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 12 Jan 2024 20:07:09 +0800 Subject: [PATCH 298/365] refactor: centralize query controller - We already using level to determine how to search data in base query controller - Using inherit class may be redundant - Set dicomLevel, logger in request object and use them in after process --- .../controller/QIDO-RS/allPatient.js | 23 ------ .../controller/QIDO-RS/base.controller.js | 9 +-- .../controller/QIDO-RS/queryAllInstances.js | 28 -------- .../controller/QIDO-RS/queryAllSeries.js | 22 ------ .../controller/QIDO-RS/queryAllStudies.js | 28 -------- .../QIDO-RS/queryStudies-Instances.js | 28 -------- .../QIDO-RS/queryStudies-Series-Instance.js | 29 -------- .../controller/QIDO-RS/queryStudies-Series.js | 27 ------- api/dicom-web/qido-rs.route.js | 71 +++++++++++++++++-- 9 files changed, 66 insertions(+), 199 deletions(-) delete mode 100644 api/dicom-web/controller/QIDO-RS/allPatient.js delete mode 100644 api/dicom-web/controller/QIDO-RS/queryAllInstances.js delete mode 100644 api/dicom-web/controller/QIDO-RS/queryAllSeries.js delete mode 100644 api/dicom-web/controller/QIDO-RS/queryAllStudies.js delete mode 100644 api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js delete mode 100644 api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js delete mode 100644 api/dicom-web/controller/QIDO-RS/queryStudies-Series.js diff --git a/api/dicom-web/controller/QIDO-RS/allPatient.js b/api/dicom-web/controller/QIDO-RS/allPatient.js deleted file mode 100644 index 852f22f4..00000000 --- a/api/dicom-web/controller/QIDO-RS/allPatient.js +++ /dev/null @@ -1,23 +0,0 @@ -const { BaseQueryController } = require("./base.controller"); - -class QueryAllPatientsController extends BaseQueryController { - constructor(req, res) { - super(req, res); - this.level = "patient"; - } - - logAction() { - this.apiLogger.logger.info("Query all patients"); - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllPatientsController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/controller/QIDO-RS/base.controller.js b/api/dicom-web/controller/QIDO-RS/base.controller.js index 7a1dfb3b..cb89dfe9 100644 --- a/api/dicom-web/controller/QIDO-RS/base.controller.js +++ b/api/dicom-web/controller/QIDO-RS/base.controller.js @@ -6,9 +6,6 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseQueryController extends Controller { constructor(req, res) { super(req, res); - this.apiLogger = new ApiLogger(this.request, "QIDO-RS"); - this.apiLogger.addTokenValue(); - this.level = "patient"; } logAction() { @@ -16,17 +13,15 @@ class BaseQueryController extends Controller { } async mainProcess() { - this.logAction(); - try { - let qidoRsService = new QidoRsService(this.request, this.response, this.level); + let qidoRsService = new QidoRsService(this.request, this.response, this.request.dicomLevel); let foundDicomJson = await qidoRsService.getDicomJson(); if (foundDicomJson.length === 0 ) { return this.response.status(204).send(); } return this.response.status(200).set("Content-Type", "application/dicom+json").json(foundDicomJson); } catch (e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.request.logger, e); return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/QIDO-RS/queryAllInstances.js b/api/dicom-web/controller/QIDO-RS/queryAllInstances.js deleted file mode 100644 index 5378306e..00000000 --- a/api/dicom-web/controller/QIDO-RS/queryAllInstances.js +++ /dev/null @@ -1,28 +0,0 @@ -const { - QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); -const { BaseQueryController } = require("./base.controller"); - -class QueryAllInstancesController extends BaseQueryController { - constructor(req, res) { - super(req, res); - this.level = "instance"; - } - - logAction() { - this.apiLogger.logger.info("Query all instances"); - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllInstancesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/controller/QIDO-RS/queryAllSeries.js b/api/dicom-web/controller/QIDO-RS/queryAllSeries.js deleted file mode 100644 index 32da7961..00000000 --- a/api/dicom-web/controller/QIDO-RS/queryAllSeries.js +++ /dev/null @@ -1,22 +0,0 @@ -const { BaseQueryController } = require("./base.controller"); - -class QueryAllSeriesController extends BaseQueryController { - constructor(req, res) { - super(req, res); - this.level = "series"; - } - - logAction() { - this.apiLogger.logger.info("Query all series"); - } -} -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllSeriesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/controller/QIDO-RS/queryAllStudies.js b/api/dicom-web/controller/QIDO-RS/queryAllStudies.js deleted file mode 100644 index 47da4ad4..00000000 --- a/api/dicom-web/controller/QIDO-RS/queryAllStudies.js +++ /dev/null @@ -1,28 +0,0 @@ -const { - QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { BaseQueryController } = require("./base.controller"); - -class QueryAllStudiesController extends BaseQueryController { - constructor(req, res) { - super(req, res); - this.level = "study"; - } - - logAction() { - this.apiLogger.logger.info(`Query All Studies`); - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryAllStudiesController(req, res); - - await controller.doPipeline(); -}; - diff --git a/api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js b/api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js deleted file mode 100644 index 57bb6c9e..00000000 --- a/api/dicom-web/controller/QIDO-RS/queryStudies-Instances.js +++ /dev/null @@ -1,28 +0,0 @@ -const { - QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); -const { BaseQueryController } = require("./base.controller"); - -class QueryInstancesOfStudiesController extends BaseQueryController { - constructor(req, res) { - super(req, res); - this.level = "instance"; - } - - logAction() { - this.apiLogger.logger.info(`Query instances in study, Study UID: ${this.request.params.studyUID}`); - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryInstancesOfStudiesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js b/api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js deleted file mode 100644 index 6e145aa5..00000000 --- a/api/dicom-web/controller/QIDO-RS/queryStudies-Series-Instance.js +++ /dev/null @@ -1,29 +0,0 @@ -const _ = require("lodash"); -const { - QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); -const { BaseQueryController } = require("./base.controller"); - -class QueryInstancesOfSeriesOfStudiesController extends BaseQueryController { - constructor(req, res) { - super(req, res); - this.level = "instance"; - } - - logAction() { - this.apiLogger.logger.info(`Query instance Level, Study UID: ${this.request.params.studyUID}, Series UID: ${this.request.params.seriesUID}`); - } -} - -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QueryInstancesOfSeriesOfStudiesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/controller/QIDO-RS/queryStudies-Series.js b/api/dicom-web/controller/QIDO-RS/queryStudies-Series.js deleted file mode 100644 index b01f936e..00000000 --- a/api/dicom-web/controller/QIDO-RS/queryStudies-Series.js +++ /dev/null @@ -1,27 +0,0 @@ -const { - QidoRsService -} = require("./service/QIDO-RS.service"); -const { ApiLogger } = require("../../../../utils/logs/api-logger"); -const { Controller } = require("../../../controller.class"); -const { BaseQueryController } = require("./base.controller"); - -class QuerySeriesOfStudiesController extends BaseQueryController { - constructor(req, res) { - super(req, res); - this.level = "series"; - } - - logAction() { - this.apiLogger.logger.info(`Query series Level, Study UID: ${this.request.params.studyUID}`); - } -} -/** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ -module.exports = async function (req, res) { - let controller = new QuerySeriesOfStudiesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/qido-rs.route.js b/api/dicom-web/qido-rs.route.js index 99bd21ee..c1930e1d 100644 --- a/api/dicom-web/qido-rs.route.js +++ b/api/dicom-web/qido-rs.route.js @@ -5,6 +5,8 @@ const router = express(); const { dictionary } = require("../../models/DICOM/dicom-tags-dic"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { BaseQueryController } = require("./controller/QIDO-RS/base.controller"); const KEYWORD_KEYS = Object.keys(dictionary.keyword); const HEX_KEYS = Object.keys(dictionary.tag); @@ -48,6 +50,10 @@ function convertKeywordToHex(attribute) { return attribute; } +const queryController = async (req, res) => { + let controller = new BaseQueryController(req, res); + await controller.doPipeline(); +}; /** * @openapi @@ -79,7 +85,13 @@ function convertKeywordToHex(attribute) { */ router.get("/studies", validateParams(queryValidation, "query", { allowUnknown: true -}), require("./controller/QIDO-RS/queryAllStudies")); +}), (req, res, next) => { + req.dicomLevel = "study"; + req.logger = new ApiLogger(req, "QIDO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Query Studies, Incoming Parameters: ${JSON.stringify(req.query)}`); + next(); +}, queryController); /** * @openapi @@ -117,7 +129,14 @@ router.get( "/studies/:studyUID/series", validateParams(queryValidation, "query", { allowUnknown: true }), - require("./controller/QIDO-RS/queryStudies-Series") + (req, res, next) => { + req.dicomLevel = "series"; + req.logger = new ApiLogger(req, "QIDO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info("Query Study's Series, Incoming Parameters: " + JSON.stringify(req.query)); + next(); + }, + queryController ); /** @@ -159,7 +178,14 @@ router.get( "/studies/:studyUID/instances", validateParams(queryValidation, "query", { allowUnknown: true }), - require("./controller/QIDO-RS/queryStudies-Instances") + (req, res, next) => { + req.dicomLevel = "instance"; + req.logger = new ApiLogger(req, "QIDO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info("Query Study's Instances, Incoming Parameters: " + JSON.stringify(req.query)); + next(); + }, + queryController ); /** @@ -202,7 +228,14 @@ router.get( "/studies/:studyUID/series/:seriesUID/instances", validateParams(queryValidation, "query", { allowUnknown: true }), - require("./controller/QIDO-RS/queryStudies-Series-Instance") + (req, res, next) => { + req.dicomLevel = "instance"; + req.logger = new ApiLogger(req, "QIDO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info("Query Study's Series' Instances, Incoming Parameters: " + JSON.stringify(req.query)); + next(); + }, + queryController ); /** @@ -241,7 +274,14 @@ router.get( "/series", validateParams(queryValidation, "query", { allowUnknown: true }), - require("./controller/QIDO-RS/queryAllSeries") + (req, res, next) => { + req.dicomLevel = "series"; + req.logger = new ApiLogger(req, "QIDO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info("Query All Series, Incoming Parameters: " + JSON.stringify(req.query)); + next(); + }, + queryController ); /** @@ -282,7 +322,14 @@ router.get( "/instances", validateParams(queryValidation, "query", { allowUnknown: true }), - require("./controller/QIDO-RS/queryAllInstances") + (req, res, next) => { + req.dicomLevel = "instance"; + req.logger = new ApiLogger(req, "QIDO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info("Query All Instances, Incoming Parameters: " + JSON.stringify(req.query)); + next(); + }, + queryController ); /** @@ -310,7 +357,17 @@ router.get( */ router.get( "/patients", - require("./controller/QIDO-RS/allPatient") + validateParams({limit: queryValidation.limit, offset: queryValidation.offset}, "query", { + allowUnknown: true + }), + (req, res, next) => { + req.dicomLevel = "patient"; + req.logger = new ApiLogger(req, "QIDO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info("Query All Patients, Incoming Parameters: " + JSON.stringify(req.query)); + next(); + }, + queryController ); //#endregion From b83979b81381df37106cf22b3e724fe33ebc8b9b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 13:25:01 +0800 Subject: [PATCH 299/365] refactor: centralize retrieve instance controller - Using inherit class may be redundant - Set `zipResponseHandlerType`, `logger`, `multipartResponseHandlerType` in request object and use them in after process --- .../controller/WADO-RS/retrieveInstance.js | 25 ---------- ...ler.js => retrieveInstances.controller.js} | 10 ++-- .../WADO-RS/retrieveStudy-Series-Instances.js | 24 --------- .../WADO-RS/retrieveStudyInstances.js | 24 --------- api/dicom-web/wado-rs-instance.route.js | 50 +++++++++++++++++-- 5 files changed, 50 insertions(+), 83 deletions(-) delete mode 100644 api/dicom-web/controller/WADO-RS/retrieveInstance.js rename api/dicom-web/controller/WADO-RS/{base.controller.js => retrieveInstances.controller.js} (91%) delete mode 100644 api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js delete mode 100644 api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js diff --git a/api/dicom-web/controller/WADO-RS/retrieveInstance.js b/api/dicom-web/controller/WADO-RS/retrieveInstance.js deleted file mode 100644 index 6cbfd394..00000000 --- a/api/dicom-web/controller/WADO-RS/retrieveInstance.js +++ /dev/null @@ -1,25 +0,0 @@ -const { BaseRetrieveController, InstanceZipResponseHandler, InstanceMultipartRelatedResponseHandler } = require("./base.controller"); -class RetrieveInstanceOfSeriesOfStudiesController extends BaseRetrieveController { - constructor(req, res) { - super(req, res); - this.zipResponseHandlerType = InstanceZipResponseHandler; - this.multipartResponseHandlerType = InstanceMultipartRelatedResponseHandler; - } - - logAction() { - this.apiLogger.logger.info(`Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); - this.apiLogger.logger.info(`Request Accept: ${this.request.headers.accept}`); - } -} - - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveInstanceOfSeriesOfStudiesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/base.controller.js b/api/dicom-web/controller/WADO-RS/retrieveInstances.controller.js similarity index 91% rename from api/dicom-web/controller/WADO-RS/base.controller.js rename to api/dicom-web/controller/WADO-RS/retrieveInstances.controller.js index bc4c30be..95b21456 100644 --- a/api/dicom-web/controller/WADO-RS/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/retrieveInstances.controller.js @@ -17,10 +17,6 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseRetrieveController extends Controller { constructor(req, res) { super(req, res); - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - this.zipResponseHandlerType = BaseZipResponseHandler; - this.multipartResponseHandlerType = BaseMultipartRelatedResponseHandler; } logAction() { @@ -40,18 +36,18 @@ class BaseRetrieveController extends Controller { return sendNotSupportedMediaType(this.response, this.request.headers.accept); } catch (e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.request.logger, e); return apiErrorArrayHandler.doErrorResponse(); } } async responseZip() { - let zipResponseHandler = new this.zipResponseHandlerType(this.request, this.response); + let zipResponseHandler = new this.request.zipResponseHandlerType(this.request, this.response); await zipResponseHandler.doResponse(); } async responseMultipartRelated() { - let multipartResponseHandler = new this.multipartResponseHandlerType(this.request, this.response); + let multipartResponseHandler = new this.request.multipartResponseHandlerType(this.request, this.response); await multipartResponseHandler.doResponse(); } } diff --git a/api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js b/api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js deleted file mode 100644 index f038b896..00000000 --- a/api/dicom-web/controller/WADO-RS/retrieveStudy-Series-Instances.js +++ /dev/null @@ -1,24 +0,0 @@ -const { BaseRetrieveController, SeriesZipResponseHandler, SeriesMultipartRelatedResponseHandler } = require("./base.controller"); - -class RetrieveInstancesOfSeries extends BaseRetrieveController { - constructor(req, res) { - super(req, res); - this.zipResponseHandlerType = SeriesZipResponseHandler; - this.multipartResponseHandlerType = SeriesMultipartRelatedResponseHandler; - } - - logAction() { - this.apiLogger.logger.info(`[WADO-RS] [Get study's series' instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}] [Request Accept: ${this.request.headers.accept}]`); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveInstancesOfSeries(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js b/api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js deleted file mode 100644 index 84c1e677..00000000 --- a/api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js +++ /dev/null @@ -1,24 +0,0 @@ -const { BaseRetrieveController, StudyZipResponseHandler, StudyMultipartRelatedResponseHandler } = require("./base.controller"); - -class RetrieveStudyInstancesController extends BaseRetrieveController { - constructor(req, res) { - super(req, res); - this.zipResponseHandlerType = StudyZipResponseHandler; - this.multipartResponseHandlerType = StudyMultipartRelatedResponseHandler; - } - - logAction() { - this.apiLogger.logger.info(`[WADO-RS] [Get study's instances, study UID: ${this.request.params.studyUID}] [Request Accept: ${this.request.headers.accept}]`); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveStudyInstancesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/wado-rs-instance.route.js b/api/dicom-web/wado-rs-instance.route.js index bdcb4ee3..a2995c94 100644 --- a/api/dicom-web/wado-rs-instance.route.js +++ b/api/dicom-web/wado-rs-instance.route.js @@ -3,6 +3,23 @@ const Joi = require("joi"); const { validateParams, intArrayJoi } = require("../validator"); const router = express(); + +const { + BaseRetrieveController, + StudyZipResponseHandler, + StudyMultipartRelatedResponseHandler, + SeriesZipResponseHandler, + SeriesMultipartRelatedResponseHandler, + InstanceZipResponseHandler, + InstanceMultipartRelatedResponseHandler +} = require("./controller/WADO-RS/retrieveInstances.controller"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); + +let retrieveController = async (req, res) => { + let controller = new BaseRetrieveController(req, res); + await controller.doPipeline(); +}; + //#region WADO-RS Retrieve Transaction Instance Resources /** @@ -21,7 +38,15 @@ const router = express(); */ router.get( "/studies/:studyUID", - require("./controller/WADO-RS/retrieveStudyInstances") + (req, res, next) => { + req.zipResponseHandlerType = StudyZipResponseHandler; + req.multipartResponseHandlerType = StudyMultipartRelatedResponseHandler; + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`[Get study's instances, study UID: ${req.params.studyUID}] [Request Accept: ${req.headers?.accept}]`); + next(); + }, + retrieveController ); /** @@ -41,7 +66,15 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID", - require("./controller/WADO-RS/retrieveStudy-Series-Instances") + (req, res, next) => { + req.zipResponseHandlerType = SeriesZipResponseHandler; + req.multipartResponseHandlerType = SeriesMultipartRelatedResponseHandler; + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`[Get study's series' instances, study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID}] [Request Accept: ${req.headers?.accept}]`); + next(); + }, + retrieveController ); /** @@ -62,7 +95,18 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/instances/:instanceUID", - require("./controller/WADO-RS/retrieveInstance") + (req, res, next) => { + req.zipResponseHandlerType = InstanceZipResponseHandler; + req.multipartResponseHandlerType = InstanceMultipartRelatedResponseHandler; + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`[Get study's series' instance,`+ + `study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID}, instance UID: ${req.params.instanceUID}]` + + ` [Request Accept: ${req.headers?.accept}]`); + + next(); + }, + retrieveController ); //#endregion From f01dc822459c2176c198570aaa026fc2402d814a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 13:53:08 +0800 Subject: [PATCH 300/365] refactor: centralize retrieve thumbnail controller - Using inherit class may be redundant - Set `factory`, `logger` in request object and use them in after process --- .../controller/WADO-RS/thumbnail/frame.js | 28 ----------- .../controller/WADO-RS/thumbnail/instance.js | 30 ------------ ...ler.js => retrieveThumbnail.controller.js} | 12 +---- .../controller/WADO-RS/thumbnail/series.js | 32 ------------- .../controller/WADO-RS/thumbnail/study.js | 25 ---------- api/dicom-web/wado-rs-thumbnail.route.js | 48 +++++++++++++++++-- 6 files changed, 45 insertions(+), 130 deletions(-) delete mode 100644 api/dicom-web/controller/WADO-RS/thumbnail/frame.js delete mode 100644 api/dicom-web/controller/WADO-RS/thumbnail/instance.js rename api/dicom-web/controller/WADO-RS/thumbnail/{base.controller.js => retrieveThumbnail.controller.js} (70%) delete mode 100644 api/dicom-web/controller/WADO-RS/thumbnail/series.js delete mode 100644 api/dicom-web/controller/WADO-RS/thumbnail/study.js diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/frame.js b/api/dicom-web/controller/WADO-RS/thumbnail/frame.js deleted file mode 100644 index 602be5a8..00000000 --- a/api/dicom-web/controller/WADO-RS/thumbnail/frame.js +++ /dev/null @@ -1,28 +0,0 @@ -const { - InstanceThumbnailFactory -} = require("../service/thumbnail.service"); -const { BaseThumbnailController } = require("./base.controller"); - -class RetrieveFrameThumbnailController extends BaseThumbnailController { - constructor(req, res) { - super(req, res); - this.factory = InstanceThumbnailFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [series UID: ${this.request.params.seriesUID}]\ -instance UID: ${this.request.params.instanceUID}\ -frames: ${JSON.stringify(this.request.params.frameNumber)}`); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveFrameThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/instance.js b/api/dicom-web/controller/WADO-RS/thumbnail/instance.js deleted file mode 100644 index 48ae2241..00000000 --- a/api/dicom-web/controller/WADO-RS/thumbnail/instance.js +++ /dev/null @@ -1,30 +0,0 @@ -const { - InstanceThumbnailFactory -} = require("../service/thumbnail.service"); -const { BaseThumbnailController } = require("./base.controller"); - - - -class RetrieveInstanceThumbnailController extends BaseThumbnailController { - constructor(req, res) { - super(req, res); - this.factory = InstanceThumbnailFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get Study's Series' Instance Thumbnail [study UID: ${this.request.params.studyUID},\ -series UID: ${this.request.params.seriesUID}]\ -instance UID: ${this.request.params.instanceUID}`); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveInstanceThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js b/api/dicom-web/controller/WADO-RS/thumbnail/retrieveThumbnail.controller.js similarity index 70% rename from api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js rename to api/dicom-web/controller/WADO-RS/thumbnail/retrieveThumbnail.controller.js index c96cb0df..e3e50c2b 100644 --- a/api/dicom-web/controller/WADO-RS/thumbnail/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/thumbnail/retrieveThumbnail.controller.js @@ -7,23 +7,15 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseThumbnailController extends Controller { constructor(req, res) { super(req, res); - this.factory = StudyImagePathFactory; - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - } - - logAction() { - this.apiLogger.logger.info(`Get Study's Thumbnail [study UID: ${this.request.params.studyUID}]`); } async mainProcess() { try { - this.logAction(); - let thumbnailService = new ThumbnailService(this.request, this.response, this.apiLogger, this.factory); + let thumbnailService = new ThumbnailService(this.request, this.response, this.request.logger, this.request.factory); let thumbnail = await thumbnailService.getThumbnail(); return this.response.end(thumbnail, "binary"); } catch (e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.request.logger, e); return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/series.js b/api/dicom-web/controller/WADO-RS/thumbnail/series.js deleted file mode 100644 index 2ac25881..00000000 --- a/api/dicom-web/controller/WADO-RS/thumbnail/series.js +++ /dev/null @@ -1,32 +0,0 @@ -const { Controller } = require("../../../../controller.class"); -const { ApiLogger } = require("../../../../../utils/logs/api-logger"); -const { - ThumbnailService, - SeriesThumbnailFactory -} = require("../service/thumbnail.service"); -const { BaseThumbnailController } = require("./base.controller"); - - - -class RetrieveSeriesThumbnailController extends BaseThumbnailController { - constructor(req, res) { - super(req, res); - this.factory = SeriesThumbnailFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get Study's Series' Thumbnail [study UID: ${this.request.params.studyUID},\ -series UID: ${this.request.params.seriesUID}]`); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveSeriesThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/thumbnail/study.js b/api/dicom-web/controller/WADO-RS/thumbnail/study.js deleted file mode 100644 index 6ddcbaf4..00000000 --- a/api/dicom-web/controller/WADO-RS/thumbnail/study.js +++ /dev/null @@ -1,25 +0,0 @@ -const { - StudyThumbnailFactory -} = require("../service/thumbnail.service"); -const { - BaseThumbnailController -} = require("./base.controller"); - - -class RetrieveStudyThumbnailController extends BaseThumbnailController { - constructor(req, res) { - super(req, res); - this.factory = StudyThumbnailFactory; - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function (req, res) { - let controller = new RetrieveStudyThumbnailController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/wado-rs-thumbnail.route.js b/api/dicom-web/wado-rs-thumbnail.route.js index 9b9e6c4c..d33f2edb 100644 --- a/api/dicom-web/wado-rs-thumbnail.route.js +++ b/api/dicom-web/wado-rs-thumbnail.route.js @@ -3,6 +3,14 @@ const Joi = require("joi"); const { validateParams, intArrayJoi } = require("../validator"); const router = express(); +const { BaseThumbnailController } = require("./controller/WADO-RS/thumbnail/retrieveThumbnail.controller"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StudyThumbnailFactory, SeriesThumbnailFactory, InstanceThumbnailFactory } = require("./controller/WADO-RS/service/thumbnail.service"); + +const RetrieveThumbnailController = async function (req, res) { + let controller = new BaseThumbnailController(req, res); + await controller.doPipeline(); +}; //#region WADO-RS Retrieve Transaction Thumbnail Resources @@ -27,7 +35,14 @@ const router = express(); */ router.get( "/studies/:studyUID/thumbnail", - require("./controller/WADO-RS/thumbnail/study") + (req, res, next) => { + req.factory = StudyThumbnailFactory; + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get Study's Thumbnail [study UID: ${req.params.studyUID}]`); + next(); + }, + RetrieveThumbnailController ); /** @@ -52,7 +67,14 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/thumbnail", - require("./controller/WADO-RS/thumbnail/series") + (req, res, next) => { + req.factory = SeriesThumbnailFactory; + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get Study's Series' Thumbnail [study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID}]`); + next(); + }, + RetrieveThumbnailController ); /** @@ -78,7 +100,15 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/thumbnail", - require("./controller/WADO-RS/thumbnail/instance") + (req, res, next) => { + req.factory = InstanceThumbnailFactory; + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get Study's Series' Instance's Thumbnail [study UID: ${req.params.studyUID},`+ + ` series UID: ${req.params.seriesUID}, instance UID: ${req.params.instanceUID}]`); + next(); + }, + RetrieveThumbnailController ); /** @@ -107,8 +137,16 @@ router.get( "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/frames/:frameNumber/thumbnail", validateParams({ frameNumber : intArrayJoi.intArray().items(Joi.number().integer().min(1)).single() - } , "params" , {allowUnknown : true}), - require("./controller/WADO-RS/thumbnail/frame") + } , "params" , {allowUnknown : true}), + (req, res, next) => { + req.factory = InstanceThumbnailFactory; + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get Study's Instance's Frame's Thumbnail [study UID: ${req.params.studyUID},`+ + ` series UID: ${req.params.seriesUID}, instance UID: ${req.params.instanceUID}, frame number: ${req.params.frameNumber}]`); + next(); + }, + RetrieveThumbnailController ); From a8a25414030334f514424c083750680cdc7888c9 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 14:07:49 +0800 Subject: [PATCH 301/365] refactor: centralize retrieve rendered controller - Using inherit class may be redundant - Set `imagePathFactory`, `framesWriter`, `logger` in request object and use them in after process --- .../WADO-RS/rendered/instanceFrames.js | 31 ----------- .../controller/WADO-RS/rendered/instances.js | 31 ----------- ...ller.js => retrieveRendered.controller.js} | 22 ++------ .../controller/WADO-RS/rendered/series.js | 30 ----------- .../controller/WADO-RS/rendered/study.js | 30 ----------- api/dicom-web/wado-rs-rendered.route.js | 52 +++++++++++++++++-- 6 files changed, 51 insertions(+), 145 deletions(-) delete mode 100644 api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js delete mode 100644 api/dicom-web/controller/WADO-RS/rendered/instances.js rename api/dicom-web/controller/WADO-RS/rendered/{base.controller.js => retrieveRendered.controller.js} (78%) delete mode 100644 api/dicom-web/controller/WADO-RS/rendered/series.js delete mode 100644 api/dicom-web/controller/WADO-RS/rendered/study.js diff --git a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js b/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js deleted file mode 100644 index 07a3fa4d..00000000 --- a/api/dicom-web/controller/WADO-RS/rendered/instanceFrames.js +++ /dev/null @@ -1,31 +0,0 @@ -const _ = require("lodash"); -const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); -const { BaseRetrieveRenderedController } = require("./base.controller"); -const { InstanceFramesListWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); - -class RetrieveRenderedInstanceFramesController extends BaseRetrieveRenderedController { - constructor(req, res) { - super(req, res); - this.imagePathFactory = InstanceImagePathFactory; - this.framesWriter = InstanceFramesListWriter; - } - - logAction() { - this.apiLogger.logger.info(`Get study's series' rendered instances' frames, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}, instance UID: ${this.request.params.instanceUID}, frame: ${this.request.params.frameNumber}`); - } - - logSuccessful() { - this.apiLogger.logger.info(`Get instance's frame successfully, instance UID: ${this.request.params.instanceUID}, frame number: ${JSON.stringify(this.request.params.frameNumber)}`); - } -} -/** - * - * @param {import("http").incomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedInstanceFramesController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/rendered/instances.js b/api/dicom-web/controller/WADO-RS/rendered/instances.js deleted file mode 100644 index c9c48250..00000000 --- a/api/dicom-web/controller/WADO-RS/rendered/instances.js +++ /dev/null @@ -1,31 +0,0 @@ -const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); -const { InstanceFramesWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); -const { BaseRetrieveRenderedController } = require("./base.controller"); - -class RetrieveRenderedInstancesController extends BaseRetrieveRenderedController { - constructor(req, res) { - super(req, res); - this.imagePathFactory = InstanceImagePathFactory; - this.framesWriter = InstanceFramesWriter; - } - - logAction() { - this.apiLogger.logger.info(`Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}`); - } - - logSuccessful() { - this.apiLogger.logger.info(`Write Multipart Successfully, study's series' instances' rendered images, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}, instance UID: ${this.request.params.instanceUID}`); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedInstancesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js b/api/dicom-web/controller/WADO-RS/rendered/retrieveRendered.controller.js similarity index 78% rename from api/dicom-web/controller/WADO-RS/rendered/base.controller.js rename to api/dicom-web/controller/WADO-RS/rendered/retrieveRendered.controller.js index 34d26913..62a09856 100644 --- a/api/dicom-web/controller/WADO-RS/rendered/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/rendered/retrieveRendered.controller.js @@ -16,23 +16,9 @@ class BaseRetrieveRenderedController extends Controller { */ constructor(req, res) { super(req, res); - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - this.imagePathFactory = StudyImagePathFactory; - this.framesWriter = renderedService.StudyFramesWriter; - } - - logAction() { - throw new Error("abstract, not implement"); - } - - logSuccessful() { - throw new Error("abstract, not implement"); } async mainProcess() { - this.logAction(); - let headerAccept = _.get(this.request.headers, "accept", ""); if (!headerAccept == `multipart/related; type="image/jpeg"`) { let badRequestMessage = errorResponse.getBadRequestErrorMessage(`header accept only allow \`multipart/related; type="image/jpeg"\`, exception : ${headerAccept}`); @@ -47,21 +33,19 @@ class BaseRetrieveRenderedController extends Controller { let renderedImageMultipartWriter = new renderedService.RenderedImageMultipartWriter( this.request, this.response, - this.imagePathFactory, - this.framesWriter + this.request.imagePathFactory, + this.request.framesWriter ); let buffer = await renderedImageMultipartWriter.write(); - this.logSuccessful(); - if (buffer instanceof Buffer) { return this.response.end(buffer, "binary"); } return this.response.end(); } catch(e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.request.logger, e); return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/WADO-RS/rendered/series.js b/api/dicom-web/controller/WADO-RS/rendered/series.js deleted file mode 100644 index 02620c13..00000000 --- a/api/dicom-web/controller/WADO-RS/rendered/series.js +++ /dev/null @@ -1,30 +0,0 @@ -const { SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); -const { SeriesFramesWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); -const { BaseRetrieveRenderedController } = require("./base.controller"); - -class RetrieveRenderedSeriesController extends BaseRetrieveRenderedController { - constructor(req, res) { - super(req, res); - this.imagePathFactory = SeriesImagePathFactory; - this.framesWriter = SeriesFramesWriter; - } - - logAction() { - this.apiLogger.logger.info(`[WADO-RS] [Get study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); - } - - logSuccessful() { - this.apiLogger.logger.info(`[WADO-RS] [path: ${this.request.originalUrl}] [Write Multipart Successfully, study's series' rendered instances, study UID: ${this.request.params.studyUID}, series UID: ${this.request.params.seriesUID}]`); - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedSeriesController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/controller/WADO-RS/rendered/study.js b/api/dicom-web/controller/WADO-RS/rendered/study.js deleted file mode 100644 index f5fffafa..00000000 --- a/api/dicom-web/controller/WADO-RS/rendered/study.js +++ /dev/null @@ -1,30 +0,0 @@ -const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); -const { StudyFramesWriter } = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); -const { BaseRetrieveRenderedController } = require("./base.controller"); - -class RetrieveRenderedStudyController extends BaseRetrieveRenderedController { - constructor(req, res) { - super(req, res); - this.imagePathFactory = StudyImagePathFactory; - this.framesWriter = StudyFramesWriter; - } - - logAction() { - this.apiLogger.logger.info(`Get study's rendered instances, study UID: ${this.request.params.studyUID}`); - } - - logSuccessful() { - this.apiLogger.logger.info(`Write Multipart Successfully, study's rendered instances, study UID: ${this.request.params.studyUID}`); - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - * @returns - */ -module.exports = async function(req, res) { - let controller = new RetrieveRenderedStudyController(req, res); - - await controller.doPipeline(); -}; diff --git a/api/dicom-web/wado-rs-rendered.route.js b/api/dicom-web/wado-rs-rendered.route.js index 6e07187d..726c33cf 100644 --- a/api/dicom-web/wado-rs-rendered.route.js +++ b/api/dicom-web/wado-rs-rendered.route.js @@ -3,6 +3,14 @@ const Joi = require("joi"); const { validateParams, intArrayJoi } = require("../validator"); const router = express(); +const { BaseRetrieveRenderedController } = require("./controller/WADO-RS/rendered/retrieveRendered.controller"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StudyImagePathFactory, SeriesImagePathFactory, InstanceImagePathFactory } = require("./controller/WADO-RS/service/WADO-RS.service"); +const { StudyFramesWriter, SeriesFramesWriter, InstanceFramesWriter, InstanceFramesListWriter } = require("./controller/WADO-RS/service/rendered.service"); +const RetrieveRenderedController = async (req, res) => { + let controller = new BaseRetrieveRenderedController(req, res); + await controller.doPipeline(); +}; //#region WADO-RS Retrieve Transaction Rendered Resources @@ -60,7 +68,16 @@ const renderedQueryValidation = { router.get( "/studies/:studyUID/rendered", validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/study") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(); + req.logger.logger.info(`Get study's rendered images, study UID: ${req.params.studyUID}`); + req.imagePathFactory = StudyImagePathFactory; + req.framesWriter = StudyFramesWriter; + next(); + }, + RetrieveRenderedController ); /** @@ -84,7 +101,16 @@ router.get( router.get( "/studies/:studyUID/series/:seriesUID/rendered", validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/series") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(); + req.logger.logger.info(`Get study's series' rendered images, study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID}`); + req.imagePathFactory = SeriesImagePathFactory; + req.framesWriter = SeriesFramesWriter; + next(); + }, + RetrieveRenderedController ); /** @@ -109,7 +135,16 @@ router.get( router.get( "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/rendered", validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/instances") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get study's series' instance rendered images, study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID}`+ + `, instance UID: ${req.params.instanceUID}`); + req.imagePathFactory = InstanceImagePathFactory; + req.framesWriter = InstanceFramesWriter; + next(); + }, + RetrieveRenderedController ); /** @@ -138,7 +173,16 @@ router.get( frameNumber : intArrayJoi.intArray().items(Joi.number().integer().min(1)).single() } , "params" , {allowUnknown : true}), validateParams(renderedQueryValidation, "query", { allowUnknown: false }), - require("./controller/WADO-RS/rendered/instanceFrames") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get instance' rendered frames, study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID}`+ + `, instance UID: ${req.params.instanceUID}, frame numbers: ${req.params.frameNumber}`); + req.imagePathFactory = InstanceImagePathFactory; + req.framesWriter = InstanceFramesListWriter; + next(); + }, + RetrieveRenderedController ); //#endregion From 73004a710e06ea17e6b1753700eaee35d1dd9a1e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 14:12:32 +0800 Subject: [PATCH 302/365] refactor: centralize retrieve metadata controller - Using inherit class may be redundant - Set `imagePathFactory`, `logger` in request object and use them in after process --- .../metadata/retrieveInstanceMetadata.js | 23 ------------ ...ller.js => retrieveMetadata.controller.js} | 12 +----- .../metadata/retrieveSeriesMetadata.js | 23 ------------ .../WADO-RS/metadata/retrieveStudyMetadata.js | 24 ------------ api/dicom-web/wado-rs-metadata.route.js | 37 +++++++++++++++++-- 5 files changed, 36 insertions(+), 83 deletions(-) delete mode 100644 api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js rename api/dicom-web/controller/WADO-RS/metadata/{base.controller.js => retrieveMetadata.controller.js} (79%) delete mode 100644 api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js delete mode 100644 api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js deleted file mode 100644 index 23c94544..00000000 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveInstanceMetadata.js +++ /dev/null @@ -1,23 +0,0 @@ -const { BaseRetrieveMetadataController } = require("./base.controller"); -const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); - -class RetrieveInstanceMetadataController extends BaseRetrieveMetadataController { - constructor(req, res) { - super(req, res); - this.imagePathFactory = InstanceImagePathFactory; - } - - logAction() { - this.apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instance Metadata] [instance UID: ${this.request.params.instanceUID}, series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveInstanceMetadataController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveMetadata.controller.js similarity index 79% rename from api/dicom-web/controller/WADO-RS/metadata/base.controller.js rename to api/dicom-web/controller/WADO-RS/metadata/retrieveMetadata.controller.js index 5a53c00e..e0de42f2 100644 --- a/api/dicom-web/controller/WADO-RS/metadata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/metadata/retrieveMetadata.controller.js @@ -7,18 +7,10 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseRetrieveMetadataController extends Controller { constructor(req, res) { super(req, res); - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - this.imagePathFactory = StudyImagePathFactory; - } - - logAction() { - throw new Error("Abstract method, not implement"); } async mainProcess() { - this.logAction(); - let metadataService = new MetadataService(this.request, this.imagePathFactory); + let metadataService = new MetadataService(this.request, this.request.imagePathFactory); try { let responseMetadata = await metadataService.getMetadata(this.request.params); @@ -33,7 +25,7 @@ class BaseRetrieveMetadataController extends Controller { return this.response.end(); } catch (e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.request.logger, e); return apiErrorArrayHandler.doErrorResponse(); } } diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js deleted file mode 100644 index d04393ad..00000000 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveSeriesMetadata.js +++ /dev/null @@ -1,23 +0,0 @@ -const { BaseRetrieveMetadataController } = require("./base.controller"); -const { SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); - -class RetrieveSeriesMetadataController extends BaseRetrieveMetadataController { - constructor(req, res) { - super(req, res); - this.imagePathFactory = SeriesImagePathFactory; - } - - logAction() { - this.apiLogger.logger.info(`[WADO-RS] [Get Study's Series' Instances Metadata] [series UID: ${this.request.params.seriesUID}, study UID: ${this.request.params.studyUID}]`); - } -} -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveSeriesMetadataController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js b/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js deleted file mode 100644 index 416f5c91..00000000 --- a/api/dicom-web/controller/WADO-RS/metadata/retrieveStudyMetadata.js +++ /dev/null @@ -1,24 +0,0 @@ -const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); -const { BaseRetrieveMetadataController } = require("./base.controller"); - -class RetrieveStudyMetadataController extends BaseRetrieveMetadataController { - constructor(req, res) { - super(req, res); - this.imagePathFactory = StudyImagePathFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get Study's Instances Metadata [study UID: ${this.request.params.studyUID}]`); - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ -module.exports = async function(req, res) { - let controller = new RetrieveStudyMetadataController(req, res); - - await controller.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/wado-rs-metadata.route.js b/api/dicom-web/wado-rs-metadata.route.js index 59f15e51..acff3ecb 100644 --- a/api/dicom-web/wado-rs-metadata.route.js +++ b/api/dicom-web/wado-rs-metadata.route.js @@ -3,6 +3,13 @@ const Joi = require("joi"); const { validateParams, intArrayJoi } = require("../validator"); const router = express(); +const { BaseRetrieveMetadataController } = require("./controller/WADO-RS/metadata/retrieveMetadata.controller"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StudyImagePathFactory, SeriesImagePathFactory, InstanceImagePathFactory } = require("./controller/WADO-RS/service/WADO-RS.service"); +const RetrieveMetadataController = async (req, res) => { + let controller = new BaseRetrieveMetadataController(req, res); + await controller.doPipeline(); +}; //#region WADO-RS Retrieve Transaction Metadata Resources @@ -22,7 +29,15 @@ const router = express(); */ router.get( "/studies/:studyUID/metadata", - require("./controller/WADO-RS/metadata/retrieveStudyMetadata") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(); + req.logger.logger.info(`Get study's metadata, study UID: ${req.params.studyUID}`); + req.imagePathFactory = StudyImagePathFactory; + next(); + }, + RetrieveMetadataController ); /** @@ -42,7 +57,15 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/metadata", - require("./controller/WADO-RS/metadata/retrieveSeriesMetadata") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(); + req.logger.logger.info(`Get study's series's metadata, study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID}`); + req.imagePathFactory = SeriesImagePathFactory; + next(); + }, + RetrieveMetadataController ); /** @@ -63,7 +86,15 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/metadata", - require("./controller/WADO-RS/metadata/retrieveInstanceMetadata") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get instance's metadata, study UID: ${req.params.studyUID}, series UID: ${req.params.seriesUID},`+ + ` instance UID: ${req.params.instanceUID}`); + req.imagePathFactory = InstanceImagePathFactory; + next(); + }, + RetrieveMetadataController ); //#endregion From 9e4703afdc97d17697874009d6ff595f11c108cf Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 14:30:44 +0800 Subject: [PATCH 303/365] refactor: centralize retrieve bulkdata controller - Using inherit class may be redundant - Set `imagePathFactory`, `bulkDataFactoryType`, `logger` in request object and use them in after process --- .../controller/WADO-RS/bulkdata/bulkdata.js | 2 +- .../controller/WADO-RS/bulkdata/instance.js | 2 +- ...ller.js => retrieveBulkData.controller.js} | 16 ++---- .../controller/WADO-RS/bulkdata/series.js | 2 +- .../controller/WADO-RS/bulkdata/study.js | 2 +- api/dicom-web/wado-rs-bulkdata.route.js | 51 +++++++++++++++++-- 6 files changed, 54 insertions(+), 21 deletions(-) rename api/dicom-web/controller/WADO-RS/bulkdata/{base.controller.js => retrieveBulkData.controller.js} (78%) diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js index 85da4720..b8b24e0d 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js @@ -1,5 +1,5 @@ const { SpecificBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./base.controller"); +const { BaseBulkDataController } = require("./retrieveBulkData.controller"); class BulkDataController extends BaseBulkDataController { constructor(req, res) { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js index 44b76c81..f444944d 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js @@ -1,5 +1,5 @@ const { InstanceBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./base.controller"); +const { BaseBulkDataController } = require("./retrieveBulkData.controller"); const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class InstanceBulkDataController extends BaseBulkDataController { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js b/api/dicom-web/controller/WADO-RS/bulkdata/retrieveBulkData.controller.js similarity index 78% rename from api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js rename to api/dicom-web/controller/WADO-RS/bulkdata/retrieveBulkData.controller.js index 8403f66d..98fc88b8 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/retrieveBulkData.controller.js @@ -7,20 +7,10 @@ const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseBulkDataController extends Controller { constructor(req, res) { super(req, res); - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - this.bulkDataFactoryType = StudyBulkDataFactory; - this.imagePathFactoryType = StudyImagePathFactory; - this.bulkDataService = new BulkDataService(this.request, this.response, this.bulkDataFactoryType); - } - - logAction() { - throw new Error("Abstract Method not implemented."); + this.bulkDataService = new BulkDataService(this.request, this.response, this.request.bulkDataFactoryType); } async mainProcess() { - this.logAction(); - try { let bulkData = await this.bulkDataService.getBulkData(); @@ -33,7 +23,7 @@ class BaseBulkDataController extends Controller { this.bulkDataService.multipartWriter.writeFinalBoundary(); return this.response.end(); } catch (e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.request.logger, e); return apiErrorArrayHandler.doErrorResponse(); } } @@ -43,7 +33,7 @@ class BaseBulkDataController extends Controller { await this.bulkDataService.writeBulkData(bulkData); } - let imagePathFactory = new this.imagePathFactoryType({ + let imagePathFactory = new this.request.imagePathFactoryType({ ...this.request.params }); await imagePathFactory.getImagePaths(); diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/series.js b/api/dicom-web/controller/WADO-RS/bulkdata/series.js index e1a253a7..f7ec6982 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/series.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/series.js @@ -1,5 +1,5 @@ const { SeriesBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./base.controller"); +const { BaseBulkDataController } = require("./retrieveBulkData.controller"); const { SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class SeriesBulkDataController extends BaseBulkDataController { diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/study.js b/api/dicom-web/controller/WADO-RS/bulkdata/study.js index 82c5b15d..0a709ca4 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/study.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/study.js @@ -1,5 +1,5 @@ const { StudyBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./base.controller"); +const { BaseBulkDataController } = require("./retrieveBulkData.controller"); const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); class StudyBulkDataController extends BaseBulkDataController { diff --git a/api/dicom-web/wado-rs-bulkdata.route.js b/api/dicom-web/wado-rs-bulkdata.route.js index 79e76e5e..34d0d022 100644 --- a/api/dicom-web/wado-rs-bulkdata.route.js +++ b/api/dicom-web/wado-rs-bulkdata.route.js @@ -3,6 +3,16 @@ const Joi = require("joi"); const { validateParams, intArrayJoi } = require("../validator"); const router = express(); +const { BaseBulkDataController } = require("./controller/WADO-RS/bulkdata/retrieveBulkData.controller"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const { StudyBulkDataFactory, SeriesBulkDataFactory, InstanceBulkDataFactory, SpecificBulkDataFactory } = require("./controller/WADO-RS/bulkdata/service/bulkdata"); +const { StudyImagePathFactory, SeriesImagePathFactory, InstanceImagePathFactory } = require("./controller/WADO-RS/service/WADO-RS.service"); + +const BulkDataController = async (req, res) => { + let bulkDataController = new BaseBulkDataController(req, res); + await bulkDataController.doPipeline(); +}; + /** * @openapi * /dicom-web/studies/{studyUID}/bulkdata: @@ -19,7 +29,15 @@ const router = express(); */ router.get( "/studies/:studyUID/bulkdata", - require("./controller/WADO-RS/bulkdata/study") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get bulk data from study: ${req.params.studyUID}`); + req.bulkDataFactoryType = StudyBulkDataFactory; + req.imagePathFactoryType = StudyImagePathFactory; + next(); + }, + BulkDataController ); /** @@ -39,7 +57,15 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/bulkdata", - require("./controller/WADO-RS/bulkdata/series") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get series' bulk data from study: ${req.params.studyUID}, series: ${req.params.seriesUID}`); + req.bulkDataFactoryType = SeriesBulkDataFactory; + req.imagePathFactoryType = SeriesImagePathFactory; + next(); + }, + BulkDataController ); /** @@ -60,7 +86,15 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/bulkdata", - require("./controller/WADO-RS/bulkdata/instance") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get instance's bulk data from study: ${req.params.studyUID}, series: ${req.params.seriesUID}, instance: ${req.params.instanceUID}`); + req.bulkDataFactoryType = InstanceBulkDataFactory; + req.imagePathFactoryType = InstanceImagePathFactory; + next(); + }, + BulkDataController ); /** @@ -82,7 +116,16 @@ router.get( */ router.get( "/studies/:studyUID/series/:seriesUID/instances/:instanceUID/bulkdata/:binaryValuePath", - require("./controller/WADO-RS/bulkdata/bulkdata") + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`Get bulk data from study: ${req.params.studyUID}, series: ${req.params.seriesUID}, instance: ${req.params.instanceUID}`+ + `, binaryValuePath: ${req.params.binaryValuePath}`); + req.bulkDataFactoryType = SpecificBulkDataFactory; + req.imagePathFactoryType = StudyImagePathFactory; + next(); + }, + BulkDataController ); module.exports = router; \ No newline at end of file From 363b6e12874b3fb6bc3faee82e264fd8b6afe337 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 14:31:16 +0800 Subject: [PATCH 304/365] chore: remove unused files --- .../controller/WADO-RS/bulkdata/bulkdata.js | 30 ------------------ .../controller/WADO-RS/bulkdata/instance.js | 31 ------------------- .../controller/WADO-RS/bulkdata/series.js | 30 ------------------ .../controller/WADO-RS/bulkdata/study.js | 29 ----------------- 4 files changed, 120 deletions(-) delete mode 100644 api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js delete mode 100644 api/dicom-web/controller/WADO-RS/bulkdata/instance.js delete mode 100644 api/dicom-web/controller/WADO-RS/bulkdata/series.js delete mode 100644 api/dicom-web/controller/WADO-RS/bulkdata/study.js diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js deleted file mode 100644 index b8b24e0d..00000000 --- a/api/dicom-web/controller/WADO-RS/bulkdata/bulkdata.js +++ /dev/null @@ -1,30 +0,0 @@ -const { SpecificBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./retrieveBulkData.controller"); - -class BulkDataController extends BaseBulkDataController { - constructor(req, res) { - super(req, res); - this.bulkDataFactoryType = SpecificBulkDataFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get bulk data ${this.request.params.binaryValuePath}\ -, from StudyInstanceUID: ${this.request.params.studyUID}\ -, SeriesInstanceUID: ${this.request.params.seriesUID}\ -, SOPInstanceUID: ${this.request.params.instanceUID}`); - } - -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let bulkDataController = new BulkDataController(req, res); - - await bulkDataController.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js b/api/dicom-web/controller/WADO-RS/bulkdata/instance.js deleted file mode 100644 index f444944d..00000000 --- a/api/dicom-web/controller/WADO-RS/bulkdata/instance.js +++ /dev/null @@ -1,31 +0,0 @@ -const { InstanceBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./retrieveBulkData.controller"); -const { InstanceImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); - -class InstanceBulkDataController extends BaseBulkDataController { - constructor(req, res) { - super(req, res); - this.bulkDataFactoryType = InstanceBulkDataFactory; - this.imagePathFactoryType = InstanceImagePathFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ -, SeriesInstanceUID: ${this.request.params.seriesUID}\ -, SOPInstanceUID: ${this.request.params.instanceUID}`); - } - -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function (req, res) { - let instanceBulkDataController = new InstanceBulkDataController(req, res); - - await instanceBulkDataController.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/series.js b/api/dicom-web/controller/WADO-RS/bulkdata/series.js deleted file mode 100644 index f7ec6982..00000000 --- a/api/dicom-web/controller/WADO-RS/bulkdata/series.js +++ /dev/null @@ -1,30 +0,0 @@ -const { SeriesBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./retrieveBulkData.controller"); -const { SeriesImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); - -class SeriesBulkDataController extends BaseBulkDataController { - constructor(req, res) { - super(req, res); - this.bulkDataFactoryType = SeriesBulkDataFactory; - this.imagePathFactoryType = SeriesImagePathFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}\ -, SeriesInstanceUID: ${this.request.params.seriesUID}`); - } - -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function (req, res) { - let seriesBulkDataController = new SeriesBulkDataController(req, res); - - await seriesBulkDataController.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/study.js b/api/dicom-web/controller/WADO-RS/bulkdata/study.js deleted file mode 100644 index 0a709ca4..00000000 --- a/api/dicom-web/controller/WADO-RS/bulkdata/study.js +++ /dev/null @@ -1,29 +0,0 @@ -const { StudyBulkDataFactory } = require("@api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { BaseBulkDataController } = require("./retrieveBulkData.controller"); -const { StudyImagePathFactory } = require("@api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); - -class StudyBulkDataController extends BaseBulkDataController { - constructor(req, res) { - super(req, res); - this.bulkDataFactoryType = StudyBulkDataFactory; - this.imagePathFactoryType = StudyImagePathFactory; - } - - logAction() { - this.apiLogger.logger.info(`Get bulk data from StudyInstanceUID: ${this.request.params.studyUID}`); - } - -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let studyBulkDataController = new StudyBulkDataController(req, res); - - await studyBulkDataController.doPipeline(); -}; \ No newline at end of file From 50935d51bdefd8d5cac1f3e8679e252e4eac38da Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 14:44:33 +0800 Subject: [PATCH 305/365] refactor: centralize retrieve delete controller - Using inherit class may be redundant - Set `dicomLevel`, `logger` in request object and use them in after process --- ...ase.controller.js => delete.controller.js} | 10 ++--- .../controller/WADO-RS/deletion/instance.js | 25 ----------- .../controller/WADO-RS/deletion/series.js | 25 ----------- .../controller/WADO-RS/deletion/study.js | 21 ---------- api/dicom-web/delete.route.js | 42 +++++++++++++++++-- 5 files changed, 42 insertions(+), 81 deletions(-) rename api/dicom-web/controller/WADO-RS/deletion/{base.controller.js => delete.controller.js} (73%) delete mode 100644 api/dicom-web/controller/WADO-RS/deletion/instance.js delete mode 100644 api/dicom-web/controller/WADO-RS/deletion/series.js delete mode 100644 api/dicom-web/controller/WADO-RS/deletion/study.js diff --git a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js b/api/dicom-web/controller/WADO-RS/deletion/delete.controller.js similarity index 73% rename from api/dicom-web/controller/WADO-RS/deletion/base.controller.js rename to api/dicom-web/controller/WADO-RS/deletion/delete.controller.js index e6f96059..76292723 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/base.controller.js +++ b/api/dicom-web/controller/WADO-RS/deletion/delete.controller.js @@ -1,18 +1,14 @@ const { Controller } = require("@root/api/controller.class"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); const { DeleteService } = require("@api/dicom-web/controller/WADO-RS/deletion/service/delete"); const { ApiErrorArrayHandler } = require("@error/api-errors.handler"); class BaseDeleteController extends Controller { constructor(req, res) { super(req, res); - this.apiLogger = new ApiLogger(this.request, "WADO-RS"); - this.apiLogger.addTokenValue(); - this.level = "study"; } async mainProcess() { - let deleteService = new DeleteService(this.request, this.response, this.level); + let deleteService = new DeleteService(this.request, this.response, this.request.dicomLevel); try { await deleteService.delete(); @@ -24,13 +20,13 @@ class BaseDeleteController extends Controller { Method: "DELETE" }); } catch(e) { - let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); + let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.request.logger, e); return apiErrorArrayHandler.doErrorResponse(); } } getDeleteSuccessfulMessage() { - return `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}`; + return `Delete ${this.request.dicomLevel} permanently, ${JSON.stringify(this.request.params)}`; } } diff --git a/api/dicom-web/controller/WADO-RS/deletion/instance.js b/api/dicom-web/controller/WADO-RS/deletion/instance.js deleted file mode 100644 index ce745014..00000000 --- a/api/dicom-web/controller/WADO-RS/deletion/instance.js +++ /dev/null @@ -1,25 +0,0 @@ -const { BaseDeleteController } = require("./base.controller"); - -class DeleteInstanceController extends BaseDeleteController { - constructor(req, res) { - super(req, res); - this.level = "instance"; - } - - getDeleteSuccessfulMessage() { - `Delete Study permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}, SOPInstanceUID: ${this.request.params.instanceUID}`; - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let deleteStudyController = new DeleteInstanceController(req, res); - - await deleteStudyController.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/deletion/series.js b/api/dicom-web/controller/WADO-RS/deletion/series.js deleted file mode 100644 index f8d4a440..00000000 --- a/api/dicom-web/controller/WADO-RS/deletion/series.js +++ /dev/null @@ -1,25 +0,0 @@ -const { BaseDeleteController } = require("./base.controller"); - -class DeleteSeriesController extends BaseDeleteController { - constructor(req, res) { - super(req, res); - this.level = "series"; - } - - getDeleteSuccessfulMessage() { - return `Delete Series permanently, StudyInstanceUID: ${this.request.params.studyUID}, SeriesInstanceUID: ${this.request.params.seriesUID}`; - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let deleteStudyController = new DeleteSeriesController(req, res); - - await deleteStudyController.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/controller/WADO-RS/deletion/study.js b/api/dicom-web/controller/WADO-RS/deletion/study.js deleted file mode 100644 index 441f77dd..00000000 --- a/api/dicom-web/controller/WADO-RS/deletion/study.js +++ /dev/null @@ -1,21 +0,0 @@ -const { BaseDeleteController } = require("./base.controller"); - -class DeleteStudyController extends BaseDeleteController { - constructor(req, res) { - super(req, res); - this.level = "study"; - } -} - - -/** - * - * @param {import("express").Request} - * @param {import("express").Response} - * @returns - */ -module.exports = async function(req, res) { - let deleteStudyController = new DeleteStudyController(req, res); - - await deleteStudyController.doPipeline(); -}; \ No newline at end of file diff --git a/api/dicom-web/delete.route.js b/api/dicom-web/delete.route.js index 0c6e1242..65ffe630 100644 --- a/api/dicom-web/delete.route.js +++ b/api/dicom-web/delete.route.js @@ -3,14 +3,50 @@ const Joi = require("joi"); const { validateParams, intArrayJoi } = require("../validator"); const router = express(); +const { BaseDeleteController } = require("./controller/WADO-RS/deletion/delete.controller"); +const { ApiLogger } = require("@root/utils/logs/api-logger"); +const DeleteController = async (req, res) => { + let deleteController = new BaseDeleteController(req, res); + await deleteController.doPipeline(); +}; //#region Delete -router.delete("/studies/:studyUID", require("./controller/WADO-RS/deletion/study")); +router.delete( + "/studies/:studyUID", + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`delete study: ${req.params.studyUID}`); + req.dicomLevel = "study"; + next(); + }, + DeleteController +); -router.delete("/studies/:studyUID/series/:seriesUID", require("./controller/WADO-RS/deletion/series")); +router.delete( + "/studies/:studyUID/series/:seriesUID", + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`delete series, study: ${req.params.studyUID}, series: ${req.params.seriesUID}`); + req.dicomLevel = "series"; + next(); + }, + DeleteController +); -router.delete("/studies/:studyUID/series/:seriesUID/instances/:instanceUID", require("./controller/WADO-RS/deletion/instance")); +router.delete( + "/studies/:studyUID/series/:seriesUID/instances/:instanceUID", + (req, res, next) => { + req.logger = new ApiLogger(req, "WADO-RS"); + req.logger.addTokenValue(); + req.logger.logger.info(`delete instance, study: ${req.params.studyUID}, series: ${req.params.seriesUID}, instance: ${req.params.instanceUID}`); + req.dicomLevel = "instance"; + next(); + }, + DeleteController +); //#endregion From a0b4299c4cc618afebf7b1b034f74ea507c195aa Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 13 Jan 2024 20:48:55 +0800 Subject: [PATCH 306/365] refactor: wrap `findNotSubscribedWorkItems` in repository model --- .../UPS-RS/service/subscribe.service.js | 16 ++----------- models/mongodb/models/workitems.model.js | 24 +++++++++++++------ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index d0d7f0eb..92ca30cc 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const workItemModel = require("@models/mongodb/models/workitems.model"); +const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { @@ -154,19 +154,7 @@ class SubscribeService extends BaseWorkItemService { //#endregion async findNotSubscribedWorkItems() { - return await workItemModel.find({ - $or: [ - { - subscribed: SUBSCRIPTION_STATE.NOT_SUBSCRIBED - }, - { - subscribed: { - $exists: false - } - } - ] - - }) || []; + return await WorkItemModel.findNotSubscribedWorkItems(); } } diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index 35a5ebb8..cbc5791f 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -36,6 +36,23 @@ let workItemSchema = new mongoose.Schema( toObject: { getters: true }, + statics: { + findNotSubscribedWorkItems: async function () { + return await mongoose.model("workItems").find({ + $or: [ + { + subscribed: SUBSCRIPTION_STATE.NOT_SUBSCRIBED + }, + { + subscribed: { + $exists: false + } + } + ] + + }) || []; + } + }, methods: { toDicomJsonModel: function () { return new DicomJsonModel(this); @@ -110,13 +127,6 @@ function getWorkItemFields() { }; } -/** - * @typedef { mongoose.Model & { - * getDicomJson: function(import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions): Promise - * }} WorkItemsModel -*/ - -/** @type {WorkItemsModel} */ let workItemModel = mongoose.model( "workItems", workItemSchema, From 92b67c08bbda5838d7c650d6a16d9be500324235 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Tue, 16 Jan 2024 23:01:21 +0800 Subject: [PATCH 307/365] refactor: wrap methods for bulk data model - wrap methods that we can change to another repository easier --- .../WADO-RS/bulkdata/service/bulkdata.js | 85 ++++-------- .../WADO-RS/service/rendered.service.js | 13 +- models/DICOM/dicom-json-model.js | 55 ++++---- models/mongodb/models/dicomBulkData.model.js | 128 +++++++++++++++++- 4 files changed, 177 insertions(+), 104 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index efbe1e81..2c8fecc8 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -1,6 +1,6 @@ const fs = require("fs"); const path = require("path"); -const dicomBulkDataModel = require("@dbModels/dicomBulkData.model"); +const { DicomBulkDataModel } = require("@dbModels/dicomBulkData.model"); const { MultipartWriter } = require("../../../../../../utils/multipartWriter"); const { streamToBuffer } = require("@jorgeferrero/stream-to-buffer"); const { raccoonConfig } = require("../../../../../../config-class"); @@ -15,7 +15,7 @@ class BulkDataService { constructor(req, res, bulkDataFactory) { this.request = req; this.response = res; - this.bulkDataFactory = new bulkDataFactory({...this.request.params}); + this.bulkDataFactory = new bulkDataFactory({ ...this.request.params }); this.multipartWriter = new MultipartWriter([], req, res); this.multipartWriter.setHeaderMultipartRelatedContentType("application/octet-stream"); } @@ -28,7 +28,7 @@ class BulkDataService { async writeBulkData(bulkData) { let absFilename; // is imagePathObj - if(bulkData.instancePath) { + if (bulkData.instancePath) { absFilename = bulkData.instancePath; } else { absFilename = path.join(raccoonConfig.dicomWebConfig.storeRootPath, bulkData.filename); @@ -39,7 +39,7 @@ class BulkDataService { this.multipartWriter.writeBoundary(); this.multipartWriter.writeContentType("application/octet-stream"); this.multipartWriter.writeContentLength(fileBuffer.length); - + let urlPath = `/dicom-web/studies/${bulkData.studyUID}/series/${bulkData.seriesUID}/instances/${bulkData.instanceUID}/bulkdata/${bulkData.binaryValuePath}`; if (bulkData.instancePath) { urlPath = `/dicom-web/studies/${bulkData.studyUID}/series/${bulkData.seriesUID}/instances/${bulkData.instanceUID}`; @@ -51,7 +51,7 @@ class BulkDataService { async getBulkData() { return await this.bulkDataFactory.getBulkData(); } - + } class BulkDataFactory { @@ -79,15 +79,7 @@ class StudyBulkDataFactory extends BulkDataFactory { studyUID } = this.uids; - let studyBulkDataArray = await dicomBulkDataModel.find({ - $and: [ - { - studyUID - } - ] - }).exec(); - - return studyBulkDataArray; + return await DicomBulkDataModel.findStudyBulkData({ studyUID }); } } @@ -102,18 +94,11 @@ class SeriesBulkDataFactory extends BulkDataFactory { seriesUID } = this.uids; - let seriesBulkDataArray = await dicomBulkDataModel.find({ - $and: [ - { - studyUID - }, - { - seriesUID - } - ] - }).exec(); - - return seriesBulkDataArray; + return await DicomBulkDataModel.findSeriesBulkData({ + studyUID, + seriesUID + }); + } } @@ -130,21 +115,11 @@ class InstanceBulkDataFactory extends BulkDataFactory { instanceUID } = this.uids; - let instanceBulkDataArray = await dicomBulkDataModel.find({ - $and: [ - { - studyUID - }, - { - seriesUID - }, - { - instanceUID - } - ] - }).exec(); - - return instanceBulkDataArray; + return await DicomBulkDataModel.findInstanceBulkData({ + studyUID, + seriesUID, + instanceUID + }); } } @@ -162,27 +137,13 @@ class SpecificBulkDataFactory extends BulkDataFactory { binaryValuePath } = this.uids; - let bulkData = await dicomBulkDataModel.findOne({ - $and: [ - { - studyUID - }, - { - seriesUID - }, - { - instanceUID - }, - { - binaryValuePath: { - $regex: `^${binaryValuePath}`, - $options: "m" - } - } - ] - }).exec(); - - return bulkData; + return await DicomBulkDataModel.findSpecificBulkData({ + studyUID, + seriesUID, + instanceUID, + binaryValuePath + }); + } } diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index ae0eae78..1c376f2e 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -13,6 +13,7 @@ const errorResponse = require("../../../../../utils/errorResponse/errorResponseM const { logger } = require("../../../../../utils/logs/log"); const { raccoonConfig } = require("../../../../../config-class"); +const { DicomBulkDataModel } = require("@dbModels/dicomBulkData.model"); class RenderedImageProcessParameterHandler { #params; @@ -40,15 +41,9 @@ class RenderedImageProcessParameterHandler { let iccProfileAction = { "no": async () => { }, "yes": async () => { - let iccProfileBinaryFile = await mongoose.model("dicomBulkData").findOne({ - $and: [ - { - binaryValuePath: "00480105.Value.0.00282000.InlineBinary" - }, - { - instanceUID: instanceID - } - ] + let iccProfileBinaryFile = await DicomBulkDataModel.findOneBulkData({ + binaryValuePath: "00480105.Value.0.00282000.InlineBinary", + instanceUID: instanceID }); if (!iccProfileBinaryFile) throw new Error("The Image dose not have icc profile tag"); let iccProfileSrc = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename); diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index ac773bc7..9f995435 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -23,7 +23,7 @@ const { dictionary } = require("./dicom-tags-dic"); * *Remove tags when processing that not use them. */ const BIG_VALUE_TAGS = [ - "52009230", + "52009230", "00480200" ]; @@ -35,7 +35,7 @@ class BaseDicomJson { */ constructor(dicomJson, ...selection) { this.dicomJson = dicomJson; - if(selection.length > 0) { + if (selection.length > 0) { this.initSelectionJson(selection); } } @@ -46,7 +46,7 @@ class BaseDicomJson { */ initSelectionJson(selection) { let selectionJson = {}; - for(let i = 0; i < selection.length; i++) { + for (let i = 0; i < selection.length; i++) { let tag = selection[i]; let item = this.getItem(tag); if (item) { @@ -86,7 +86,7 @@ class BaseDicomJson { return _.get(dictionary.tag, `${tag}`); } - static getTagOfKeyword(keyword) { + static getTagOfKeyword(keyword) { return _.get(dictionary.keyword, `${keyword}`); } @@ -158,7 +158,7 @@ class DicomJsonModel { this.storeSeriesCollection(dbJson), this.storePatientCollection(dbJson) ]); - } catch(e) { + } catch (e) { throw e; } } @@ -346,7 +346,7 @@ class DicomJsonModel { } else { startedDate = moment(startedDate, "YYYYMMDDhhmmss").toISOString(); } - + return startedDate; } @@ -360,14 +360,14 @@ class DicomJsonModel { }; } - getPatientDicomJson(dicomJson=undefined) { + getPatientDicomJson(dicomJson = undefined) { if (!dicomJson) dicomJson = this.dicomJson; let patientDicomJson = { patientID: dicomJson.patientID }; - for(let tag in tagsNeedStore.Patient) { + for (let tag in tagsNeedStore.Patient) { let value = _.get(dicomJson, tag); if (value) _.set(patientDicomJson, tag, value); @@ -376,7 +376,7 @@ class DicomJsonModel { return patientDicomJson; } - getStudyDicomJson(dicomJson=undefined) { + getStudyDicomJson(dicomJson = undefined) { if (!dicomJson) dicomJson = this.dicomJson; @@ -385,7 +385,7 @@ class DicomJsonModel { studyPath: dicomJson.studyPath }; - for(let tag in tagsNeedStore.Study) { + for (let tag in tagsNeedStore.Study) { let value = _.get(dicomJson, tag); if (value) _.set(studyDicomJson, tag, value); @@ -399,7 +399,7 @@ class DicomJsonModel { }; } - getSeriesDicomJson(dicomJson=undefined) { + getSeriesDicomJson(dicomJson = undefined) { if (!dicomJson) dicomJson = this.dicomJson; let seriesDicomJson = { @@ -408,9 +408,9 @@ class DicomJsonModel { seriesPath: dicomJson.seriesPath }; - for(let tag in tagsNeedStore.Series) { + for (let tag in tagsNeedStore.Series) { let value = _.get(dicomJson, tag); - if(value) + if (value) _.set(seriesDicomJson, tag, value); } @@ -426,7 +426,7 @@ class DicomJsonModel { class DicomJsonBinaryDataModel { - constructor(dicomJsonModel, bulkDataModelClass=BulkData) { + constructor(dicomJsonModel, bulkDataModelClass = BulkData) { /** @type {DicomJsonModel} */ this.dicomJsonModel = dicomJsonModel; @@ -455,7 +455,7 @@ class DicomJsonBinaryDataModel { getPathGroupOfBinaryProperties_() { let pathGroupOfBinaryProperties = []; - for(let binaryKey of this.binaryKeys) { + for (let binaryKey of this.binaryKeys) { if (_.get(this.dicomJsonModel.dicomJson, `${binaryKey}.Value.0`)) { pathGroupOfBinaryProperties.push(`${binaryKey}.Value.0`); @@ -476,7 +476,7 @@ class DicomJsonBinaryDataModel { sopInstanceUID } = this.dicomJsonModel.uidObj; - for(let i = 0; i < this.binaryKeys.length ; i++) { + for (let i = 0; i < this.binaryKeys.length; i++) { let binaryKey = this.binaryKeys[i]; // Reset VR to UR, because BulkDataURI is URI @@ -493,7 +493,7 @@ class DicomJsonBinaryDataModel { `${binaryKey}.BulkDataURI`, `/studies/${studyUID}/series/${seriesUID}/instances/${sopInstanceUID}/bulkdata/${pathOfBinaryProperty}` ); - + _.unset(this.dicomJsonModel.dicomJson, `${binaryKey}.InlineBinary`); } @@ -510,14 +510,14 @@ class DicomJsonBinaryDataModel { let shortInstanceUID = shortHash(sopInstanceUID); - - for(let i = 0; i < this.pathGroupOfBinaryProperties.length ; i++) { + + for (let i = 0; i < this.pathGroupOfBinaryProperties.length; i++) { let relativeFilename = `files/bulkData/${shortInstanceUID}/`; let pathOfBinaryProperty = this.pathGroupOfBinaryProperties[i]; let binaryData = _.get(this.dicomJsonModel.dicomJson, pathOfBinaryProperty); - if(binaryData) { + if (binaryData) { relativeFilename += `${pathOfBinaryProperty}.raw`; let filename = path.join( raccoonConfig.dicomWebConfig.storeRootPath, @@ -561,16 +561,10 @@ class BulkData { binaryValuePath: this.pathOfBinaryProperty }; - await dicomBulkDataModel.updateOne( + await dicomBulkDataModel.createOrUpdateBulkData( { - $and: [ - { - instanceUID: this.uidObj.sopInstanceUID - }, - { - binaryValuePath: this.pathOfBinaryProperty - } - ] + instanceUID: this.uidObj.sopInstanceUID, + binaryValuePath: this.pathOfBinaryProperty }, { studyUID: this.uidObj.studyUID, @@ -578,9 +572,6 @@ class BulkData { instanceUID: this.uidObj.sopInstanceUID, filename: this.filename, binaryValuePath: this.pathOfBinaryProperty - }, - { - upsert: true } ); logger.info(`[STOW-RS] [Store bulkdata ${JSON.stringify(item)} successful]`); diff --git a/models/mongodb/models/dicomBulkData.model.js b/models/mongodb/models/dicomBulkData.model.js index 59230537..f7efddb7 100644 --- a/models/mongodb/models/dicomBulkData.model.js +++ b/models/mongodb/models/dicomBulkData.model.js @@ -28,7 +28,131 @@ let dicomBulkDataSchema = new mongoose.Schema( }, { strict: false, - versionKey: false + versionKey: false, + statics: { + /** + * + * @param {Object} query + * @param {string} query.studyUID + * @param {string} query.seriesUID + * @param {string} query.instanceUID + * @param {string} query.binaryValuePath + * @param {any} options + * @returns + */ + findOneBulkData: async function(query, options) { + return await mongoose.model("dicomBulkData").findOne(query, options).exec(); + }, + /** + * + * @param {Object} query + * @param {string} query.studyUID + * @param {any} options + * @returns + */ + findStudyBulkData: async function(query, options) { + return await mongoose.model("dicomBulkData").find({ + $and: [ + { + studyUID: query.studyUID + } + ] + }, options?.projection, options?.options).exec(); + }, + /** + * + * @param {Object} query + * @param {string} query.studyUID + * @param {string} query.seriesUID + * @param {*} options + * @returns + */ + findSeriesBulkData: async function(query, options) { + return await mongoose.model("dicomBulkData").find({ + $and: [ + { + studyUID: query.studyUID + }, + { + seriesUID: query.seriesUID + } + ] + }, options?.projection, options?.options).exec(); + }, + /** + * + * @param {Object} query + * @param {string} query.studyUID + * @param {string} query.seriesUID + * @param {string} query.instanceUID + * @param {*} options + * @returns + */ + findInstanceBulkData: async function(query, options) { + return await mongoose.model("dicomBulkData").find({ + $and: [ + { + studyUID: query.studyUID + }, + { + seriesUID: query.seriesUID + }, + { + instanceUID: query.instanceUID + } + ] + }, options?.projection, options?.options).exec(); + }, + /** + * + * @param {Object} query + * @param {string} query.studyUID + * @param {string} query.seriesUID + * @param {string} query.instanceUID + * @param {string} query.binaryValuePath + * @param {*} options + * @returns + */ + findSpecificBulkData: async function(query, options) { + return await mongoose.model("dicomBulkData").findOne({ + $and: [ + { + studyUID: query.studyUID + }, + { + seriesUID: query.seriesUID + }, + { + instanceUID: query.instanceUID + }, + { + binaryValuePath: { + $regex: `^${query.binaryValuePath}`, + $options: "m" + } + } + ] + }, options?.projection, options?.options).exec(); + }, + createOrUpdateBulkData: async function(query, newBulkData, options) { + await mongoose.model("dicomBulkData").updateOne( + { + $and: [ + { + instanceUID: query.instanceUID + }, + { + binaryValuePath: query.binaryValuePath + } + ] + }, + newBulkData, + { + upsert: true + } + ); + } + } } ); @@ -37,4 +161,6 @@ let dicomBulkData = mongoose.model( dicomBulkDataSchema, "dicomBulkData" ); + module.exports = dicomBulkData; +module.exports.DicomBulkDataModel = dicomBulkData; From 909e0017450ccbedba8c944cd7479c40e4ac3b21 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 13:12:49 +0800 Subject: [PATCH 308/365] refactor: use `image-size` instead of `sharp` dependency - We only need height and width of image, using sharp might overkill - thus, use `image-size` instead of it --- api/WADO-URI/service/WADO-URI.service.js | 41 +- .../WADO-RS/service/rendered.service.js | 21 +- package-lock.json | 763 +----------------- package.json | 2 +- 4 files changed, 78 insertions(+), 749 deletions(-) diff --git a/api/WADO-URI/service/WADO-URI.service.js b/api/WADO-URI/service/WADO-URI.service.js index 7e3e0ad7..d40e8a6a 100644 --- a/api/WADO-URI/service/WADO-URI.service.js +++ b/api/WADO-URI/service/WADO-URI.service.js @@ -4,7 +4,9 @@ const _ = require("lodash"); const { RenderedImageProcessParameterHandler, getInstanceFrameObj } = require("../../dicom-web/controller/WADO-RS/service/rendered.service"); const { Dcm2JpgExecutor } = require("../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); -const sharp = require('sharp'); +const imageSizeOf = require("image-size"); +const { promisify } = require("util"); +const imageSizeOfPromise = promisify(imageSizeOf); const Magick = require("../../../models/magick"); const { NotFoundInstanceError, InvalidFrameNumberError, InstanceGoneError } = require("../../../error/dicom-instance"); const { InstanceModel } = require("@dbModels/instance.model"); @@ -77,13 +79,13 @@ class WadoUriService { async handleRequestQueryAndGetJpeg() { let { - imageSharp, + imageSize, magick } = await this.handleFrameNumberAndGetImageObj(); await this.handleImageQuality(magick); - await this.handleRegion(this.request.query, imageSharp, magick); - await this.handleRowsAndColumns(this.request.query, imageSharp, magick); + await this.handleRegion(this.request.query, imageSize, magick); + await this.handleRowsAndColumns(this.request.query, imageSize, magick); await this.handleImageICCProfile(this.request.query, magick, this.request.query.objectUID); await magick.execCommand(); @@ -126,7 +128,7 @@ class WadoUriService { if (getFrameImageStatus.status) { return { - imageSharp: sharp(jpegFile), + imageSize: await imageSizeOfPromise(jpegFile), magick: new Magick(jpegFile) }; } @@ -138,12 +140,18 @@ class WadoUriService { this.renderedImageProcessParameterHandler.handleImageQuality(magick); } - async handleRegion(param, imageSharp, magick) { + /** + * + * @param {Object} param + * @param {string} param.region + * @param {{height: number, width: number}} imageSize + * @param {Magick} magick + */ + async handleRegion(param, imageSize, magick) { if (param.region) { let [xMin, yMin, xMax, yMax] = param.region.split(",").map(v => parseFloat(v)); - let imageMetadata = await imageSharp.metadata(); - let imageWidth = imageMetadata.width; - let imageHeight = imageMetadata.height; + let imageWidth = imageSize.width; + let imageHeight = imageSize.height; let extractLeft = imageWidth * xMin; let extractTop = imageHeight * yMin; let extractWidth = imageWidth * xMax - extractLeft; @@ -152,16 +160,23 @@ class WadoUriService { } } - async handleRowsAndColumns(param, imageSharp, magick) { - let imageMetadata = await imageSharp.metadata(); + /** + * + * @param {Object} param + * @param {string} param.rows + * @param {string} param.columns + * @param {{height: number, width: number}} imageSize + * @param {Magick} magick + */ + async handleRowsAndColumns(param, imageSize, magick) { let rows = Number(param.rows); let columns = Number(param.columns); if (param.rows && param.columns) { magick.resize(rows, columns); } else if (param.rows) { - magick.resize(rows, imageMetadata.height); + magick.resize(rows, imageSize.height); } else if (param.columns) { - magick.resize(imageMetadata.width, columns); + magick.resize(imageSize.width, columns); } } diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index 1c376f2e..9ee02a16 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -1,13 +1,13 @@ +const _ = require("lodash"); +const imageSizeOf = require("image-size"); +const { promisify } = require("util"); +const imageSizeOfPromise = promisify(imageSizeOf); const path = require("path"); -const mongoose = require("mongoose"); const { InstanceModel } = require("@dbModels/instance.model"); const fs = require("fs"); -const sharp = require("sharp"); -const _ = require("lodash"); const { Dcm2JpgExecutor } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); const { MultipartWriter } = require("../../../../../utils/multipartWriter"); -const notImageSOPClass = require("../../../../../models/DICOM/dicomWEB/notImageSOPClass"); const Magick = require("../../../../../models/magick"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const { logger } = require("../../../../../utils/logs/log"); @@ -74,12 +74,11 @@ class RenderedImageProcessParameterHandler { /** * - * @param {sharp.Sharp} imageSharp + * @param {{ height: number, width: number }} imageSize * @param {Magick} magick */ - async handleViewport(imageSharp, magick) { + async handleViewport(imageSize, magick) { if (this.#params?.viewport) { - let imageMetadata = await imageSharp.metadata(); let viewportSplit = this.#params.viewport.split(",").map(v => Number(v)); if (viewportSplit.length == 2) { let [vw, vh] = viewportSplit; @@ -87,8 +86,8 @@ class RenderedImageProcessParameterHandler { } else { let [vw, vh, sx, sy, sw, sh] = viewportSplit; magick.resize(vw, vh); - if (sw == 0) sw = imageMetadata.width - sx; - if (sh == 0) sh = imageMetadata.height - sy; + if (sw == 0) sw = imageSize.width - sx; + if (sh == 0) sh = imageSize.height - sy; if (sw < 0) { magick.flip(); @@ -288,13 +287,13 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { ); if (getFrameImageStatus.status) { - let imageSharp = sharp(jpegFile); + let imageSize = await imageSizeOfPromise(jpegFile); let magick = new Magick(jpegFile); let renderedImageProcessParameterHandler = new RenderedImageProcessParameterHandler(req.query); renderedImageProcessParameterHandler.handleImageQuality(magick); await renderedImageProcessParameterHandler.handleImageICCProfile(magick, instanceFramesObj.instanceUID); - await renderedImageProcessParameterHandler.handleViewport(imageSharp, magick); + await renderedImageProcessParameterHandler.handleViewport(imageSize, magick); await magick.execCommand(); return { status: true, diff --git a/package-lock.json b/package-lock.json index 7bd7d64a..800ba978 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "flat": "^5.0.2", "formidable": "^2.0.1", "iconv-lite": "^0.6.3", + "image-size": "^1.1.1", "imagemagick-cli": "^0.5.0", "java-bridge": "^2.3.0", "joi": "^17.6.0", @@ -52,7 +53,6 @@ "request-multipart": "^1.0.0", "run-script-os": "^1.1.6", "sequelize": "^6.32.1", - "sharp": "^0.33.1", "shorthash2": "^1.0.3", "uuid": "^9.0.1", "ws": "^8.13.0" @@ -758,15 +758,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emnapi/runtime": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", - "integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint/eslintrc": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", @@ -845,437 +836,6 @@ "node": ">=6.9.0" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", - "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.0" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", - "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.0" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "macos": ">=11", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz", - "integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "macos": ">=10.13", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz", - "integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz", - "integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz", - "integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz", - "integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz", - "integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz", - "integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", - "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.28", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.0" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", - "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.0" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", - "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.28", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.0" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", - "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.0" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", - "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", - "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.0" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", - "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", - "cpu": [ - "wasm32" - ], - "optional": true, - "dependencies": { - "@emnapi/runtime": "^0.44.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", - "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", - "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2689,18 +2249,6 @@ "node": ">=10" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2717,15 +2265,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -4732,6 +4271,20 @@ "node": ">= 4" } }, + "node_modules/image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, "node_modules/imagemagick-cli": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/imagemagick-cli/-/imagemagick-cli-0.5.0.tgz", @@ -4817,11 +4370,6 @@ "node": ">= 0.10" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7129,6 +6677,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -7694,45 +7250,6 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "node_modules/sharp": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", - "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "semver": "^7.5.4" - }, - "engines": { - "libvips": ">=8.15.0", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.1", - "@img/sharp-darwin-x64": "0.33.1", - "@img/sharp-libvips-darwin-arm64": "1.0.0", - "@img/sharp-libvips-darwin-x64": "1.0.0", - "@img/sharp-libvips-linux-arm": "1.0.0", - "@img/sharp-libvips-linux-arm64": "1.0.0", - "@img/sharp-libvips-linux-s390x": "1.0.0", - "@img/sharp-libvips-linux-x64": "1.0.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", - "@img/sharp-libvips-linuxmusl-x64": "1.0.0", - "@img/sharp-linux-arm": "0.33.1", - "@img/sharp-linux-arm64": "0.33.1", - "@img/sharp-linux-s390x": "0.33.1", - "@img/sharp-linux-x64": "0.33.1", - "@img/sharp-linuxmusl-arm64": "0.33.1", - "@img/sharp-linuxmusl-x64": "0.33.1", - "@img/sharp-wasm32": "0.33.1", - "@img/sharp-win32-ia32": "0.33.1", - "@img/sharp-win32-x64": "0.33.1" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7780,14 +7297,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -9380,15 +8889,6 @@ "js-tokens": "^4.0.0" } }, - "@emnapi/runtime": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", - "integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==", - "optional": true, - "requires": { - "tslib": "^2.4.0" - } - }, "@eslint/eslintrc": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", @@ -9454,147 +8954,6 @@ "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", "dev": true }, - "@img/sharp-darwin-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", - "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", - "optional": true, - "requires": { - "@img/sharp-libvips-darwin-arm64": "1.0.0" - } - }, - "@img/sharp-darwin-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", - "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", - "optional": true, - "requires": { - "@img/sharp-libvips-darwin-x64": "1.0.0" - } - }, - "@img/sharp-libvips-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==", - "optional": true - }, - "@img/sharp-libvips-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz", - "integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==", - "optional": true - }, - "@img/sharp-libvips-linux-arm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz", - "integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==", - "optional": true - }, - "@img/sharp-libvips-linux-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz", - "integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==", - "optional": true - }, - "@img/sharp-libvips-linux-s390x": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz", - "integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==", - "optional": true - }, - "@img/sharp-libvips-linux-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz", - "integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==", - "optional": true - }, - "@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz", - "integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==", - "optional": true - }, - "@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz", - "integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==", - "optional": true - }, - "@img/sharp-linux-arm": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", - "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-arm": "1.0.0" - } - }, - "@img/sharp-linux-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", - "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-arm64": "1.0.0" - } - }, - "@img/sharp-linux-s390x": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", - "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-s390x": "1.0.0" - } - }, - "@img/sharp-linux-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", - "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-x64": "1.0.0" - } - }, - "@img/sharp-linuxmusl-arm64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", - "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", - "optional": true, - "requires": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" - } - }, - "@img/sharp-linuxmusl-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", - "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", - "optional": true, - "requires": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.0" - } - }, - "@img/sharp-wasm32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", - "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", - "optional": true, - "requires": { - "@emnapi/runtime": "^0.44.0" - } - }, - "@img/sharp-win32-ia32": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", - "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", - "optional": true - }, - "@img/sharp-win32-x64": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", - "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", - "optional": true - }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -10720,15 +10079,6 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, - "color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "requires": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -10742,15 +10092,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -12269,6 +11610,14 @@ "dev": true, "peer": true }, + "image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "requires": { + "queue": "6.0.2" + } + }, "imagemagick-cli": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/imagemagick-cli/-/imagemagick-cli-0.5.0.tgz", @@ -12336,11 +11685,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -14022,6 +13366,14 @@ "side-channel": "^1.0.4" } }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "requires": { + "inherits": "~2.0.3" + } + }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -14439,35 +13791,6 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "sharp": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", - "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", - "requires": { - "@img/sharp-darwin-arm64": "0.33.1", - "@img/sharp-darwin-x64": "0.33.1", - "@img/sharp-libvips-darwin-arm64": "1.0.0", - "@img/sharp-libvips-darwin-x64": "1.0.0", - "@img/sharp-libvips-linux-arm": "1.0.0", - "@img/sharp-libvips-linux-arm64": "1.0.0", - "@img/sharp-libvips-linux-s390x": "1.0.0", - "@img/sharp-libvips-linux-x64": "1.0.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", - "@img/sharp-libvips-linuxmusl-x64": "1.0.0", - "@img/sharp-linux-arm": "0.33.1", - "@img/sharp-linux-arm64": "0.33.1", - "@img/sharp-linux-s390x": "0.33.1", - "@img/sharp-linux-x64": "0.33.1", - "@img/sharp-linuxmusl-arm64": "0.33.1", - "@img/sharp-linuxmusl-x64": "0.33.1", - "@img/sharp-wasm32": "0.33.1", - "@img/sharp-win32-ia32": "0.33.1", - "@img/sharp-win32-x64": "0.33.1", - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "semver": "^7.5.4" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -14506,14 +13829,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - } - }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", diff --git a/package.json b/package.json index de327afd..2145072f 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "flat": "^5.0.2", "formidable": "^2.0.1", "iconv-lite": "^0.6.3", + "image-size": "^1.1.1", "imagemagick-cli": "^0.5.0", "java-bridge": "^2.3.0", "joi": "^17.6.0", @@ -98,7 +99,6 @@ "request-multipart": "^1.0.0", "run-script-os": "^1.1.6", "sequelize": "^6.32.1", - "sharp": "^0.33.1", "shorthash2": "^1.0.3", "uuid": "^9.0.1", "ws": "^8.13.0" From 9971a6c1aeaad48ee386ad16e2a88cbbb9d9edd0 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 15:15:25 +0800 Subject: [PATCH 309/365] refactor: wrap find, create methods for `WorkItemsModel` - `findOneByUpsInstanceUID` - `createWorkItemAndPatient` --- .../UPS-RS/service/create-workItem.service.js | 14 ++++------- models/mongodb/models/workitems.model.js | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 0a8a18f7..866073ee 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -1,6 +1,5 @@ const _ = require("lodash"); -const workItemModel = require("@models/mongodb/models/workitems.model"); -const { PatientModel } = require("@dbModels/patient.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { DicomWebServiceError, @@ -27,14 +26,11 @@ class CreateWorkItemService extends BaseWorkItemService { await this.dataAdjustBeforeCreatingUps(uid); await this.validateWorkItem(uid); - let patientId = this.requestWorkItem.getString("00100020"); - let patient = await PatientModel.findOneOrCreatePatient(patientId, this.requestWorkItem.dicomJson); - let workItem = new workItemModel(this.requestWorkItem.dicomJson); - let savedWorkItem = await workItem.save(); + let savedWorkItem = await WorkItemModel.createWorkItemAndPatient(this.requestWorkItem.dicomJson); this.triggerCreateEvent(savedWorkItem); - return workItem; + return savedWorkItem; } async dataAdjustBeforeCreatingUps(uid) { @@ -108,9 +104,7 @@ class CreateWorkItemService extends BaseWorkItemService { } async isUpsExist(uid) { - return await workItemModel.findOne({ - upsInstanceUID: uid - }); + return await WorkItemModel.findOneByUpsInstanceUID(uid); } } diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index cbc5791f..5722e0c0 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -5,6 +5,7 @@ const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { SUBSCRIPTION_STATE } = require("../../DICOM/ups"); const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { PatientModel } = require("./patient.model"); let workItemSchema = new mongoose.Schema( { @@ -51,6 +52,29 @@ let workItemSchema = new mongoose.Schema( ] }) || []; + }, + /** + * + * @param {Object} workItem general dicom json + */ + createWorkItemAndPatient: async function (workItem) { + let patientID = _.get(workItem, "00100020.Value.0"); + workItem.patientID = patientID; + + await PatientModel.findOneOrCreatePatient(patientID, workItem); + + let workItemDoc = new mongoose.model("workItems")(workItem); + return await workItemDoc.save(); + }, + /** + * + * @param {string} upsInstanceUID + * @returns + */ + findOneByUpsInstanceUID: async function (upsInstanceUID) { + return await mongoose.model("workItems").findOne({ + upsInstanceUID + }); } }, methods: { From c3eee390a90d1d16b9741c979ad99f8f2fc7eaf9 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 18:59:15 +0800 Subject: [PATCH 310/365] refactor: use `BaseDicomJson` instead of `DicomJsonModel` and use wrapped work item model methods - Move `getString` method into BaseDicomJson --- .../MWL-RS/service/create-mwlItem.service.js | 7 ++- .../UPS-RS/service/cancel.service.js | 12 ++--- .../service/change-workItem-state.service.js | 33 ++++++------- .../UPS-RS/service/create-workItem.service.js | 12 ++--- .../UPS-RS/service/subscribe.service.js | 20 ++++---- .../UPS-RS/service/unsubscribe.service.js | 11 ++--- .../UPS-RS/service/update-workItem.service.js | 46 +++++++++---------- models/DICOM/dicom-json-model.js | 18 ++++---- models/mongodb/models/workitems.model.js | 22 +++++++-- 9 files changed, 95 insertions(+), 86 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js index 920b355d..49cd8dd9 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js @@ -9,7 +9,7 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { DicomJsonModel, BaseDicomJson } = require("@models/DICOM/dicom-json-model"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); const { v4: uuidV4 } = require("uuid"); const shortHash = require("shorthash2"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); @@ -20,8 +20,7 @@ class CreateMwlItemService { this.request = req; this.response = res; this.requestMwlItem = /** @type {Object} */(this.request.body); - /** @type {DicomJsonModel} */ - this.requestMwlItemDicomJsonModel = new DicomJsonModel(this.requestMwlItem[0]); + this.requestMwlItemDicomJson = new BaseDicomJson(this.requestMwlItem[0]); this.apiLogger = new ApiLogger(req, "Create Mwl Item Service"); this.apiLogger.addTokenValue(); } @@ -88,7 +87,7 @@ class CreateMwlItemService { } async checkPatientExist() { - let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); + let patientID = this.requestMwlItemDicomJson.getString("00100020"); let patientCount = await PatientModel.countDocuments({ patientID }); diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index 42a82729..4f69b80c 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { DicomJsonModel, BaseDicomJson } = require("@dicom-json-model"); +const { BaseDicomJson } = require("@dicom-json-model"); const { DicomWebServiceError, DicomWebStatusCodes @@ -8,6 +8,7 @@ const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/servic const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { raccoonConfig } = require("@root/config-class"); +const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); class CancelWorkItemService extends BaseWorkItemService { @@ -23,13 +24,10 @@ class CancelWorkItemService extends BaseWorkItemService { this.requestWorkItem = /** @type {Object[]} */(this.request.body).pop(); } - async initWorkItem() { - this.workItem = await this.findOneWorkItem(this.upsInstanceUID); - } - async cancel() { - await this.initWorkItem(); - let procedureStepState = this.workItem.getString(dictionary.keyword.ProcedureStepState); + this.workItem = await WorkItemModel.findOneByUpsInstanceUID(this.upsInstanceUID); + + let procedureStepState = (await this.workItem.toDicomJson()).getString(dictionary.keyword.ProcedureStepState); if (procedureStepState === "IN PROGRESS") { //Only send cancel info event now, IMO diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index 3ded0248..ff1719c3 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -1,6 +1,5 @@ const _ = require("lodash"); const moment = require("moment"); -const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); const { @@ -9,6 +8,7 @@ const { } = require("@error/dicom-web-service"); const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); class ChangeWorkItemStateService extends BaseWorkItemService { /** @@ -19,8 +19,8 @@ class ChangeWorkItemStateService extends BaseWorkItemService { constructor(req, res) { super(req, res); this.requestState = /** @type {Object[]} */(this.request.body).pop(); - /** @type {DicomJsonModel} */ - this.requestState = new DicomJsonModel(this.requestState); + /** @type {BaseDicomJson} */ + this.requestState = new BaseDicomJson(this.requestState); this.transactionUID = this.requestState.getString("00081195"); if (!this.transactionUID) { this.response.set("Warning", "299 Raccoon: The Transaction UID is missing."); @@ -37,10 +37,11 @@ class ChangeWorkItemStateService extends BaseWorkItemService { } async changeWorkItemState() { - this.workItem = await this.findOneWorkItem(this.request.params.workItem); - - this.workItemState = this.workItem.getString("00741000"); - this.workItemTransactionUID = this.workItem.getString("00081195"); + this.workItem = await WorkItemModel.findOneByUpsInstanceUID(this.request.params.workItem); + /** @type { BaseDicomJson } */ + this.workItemDicomJson = await this.workItem.toDicomJson(); + this.workItemState = this.workItemDicomJson.getString("00741000"); + this.workItemTransactionUID = this.workItemDicomJson.getString("00081195"); let requestState = this.requestState.getString("00741000"); if (requestState === "IN PROGRESS") { @@ -59,9 +60,9 @@ class ChangeWorkItemStateService extends BaseWorkItemService { new: true }); - let updatedWorkItemDicomJson = new DicomJsonModel(updatedWorkItem.toObject()); + let updatedWorkItemDicomJson = await updatedWorkItem.toDicomJson(); - let hitSubscriptions = await this.getHitSubscriptions(updatedWorkItemDicomJson); + let hitSubscriptions = await this.getHitSubscriptions(updatedWorkItem); if (hitSubscriptions.length === 0) return; @@ -148,9 +149,9 @@ class ChangeWorkItemStateService extends BaseWorkItemService { } ensureProgressInformationSequence() { - let progressInformation = _.get(this.workItem.dicomJson, "00741002.Value"); + let progressInformation = this.workItemDicomJson.getValues("00741002"); if (!progressInformation) { - _.set(this.workItem.dicomJson, "00741002", { + _.set(this.workItemDicomJson.dicomJson, "00741002", { vr: "SQ", Value: [] }); @@ -159,9 +160,9 @@ class ChangeWorkItemStateService extends BaseWorkItemService { supplementDiscontinuationReasonCode() { this.ensureProgressInformationSequence(); - let procedureStepCancellationDateTime = _.get(this.workItem.dicomJson, "00741002.Value.0.00404052"); + let procedureStepCancellationDateTime = _.get(this.workItemDicomJson.dicomJson, "00741002.Value.0.00404052"); if (!procedureStepCancellationDateTime) { - _.set(this.workItem.dicomJson, "00741002.Value.0.00404052", { + _.set(this.workItemDicomJson.dicomJson, "00741002.Value.0.00404052", { vr: "DT", Value: [ moment().format("YYYYMMDDhhmmss.SSSSSSZZ") @@ -169,9 +170,9 @@ class ChangeWorkItemStateService extends BaseWorkItemService { }); } - let reasonCodeMeaning = _.get(this.workItem.dicomJson, "00741002.Value.0.0074100E.Value.0.00080104"); + let reasonCodeMeaning = _.get(this.workItemDicomJson.dicomJson, "00741002.Value.0.0074100E.Value.0.00080104"); if (!reasonCodeMeaning) { - _.set(this.workItem.dicomJson, "00741002.Value.0.0074100E.Value.0", { + _.set(this.workItemDicomJson.dicomJson, "00741002.Value.0.0074100E.Value.0", { vr: "SQ", Value: [ { @@ -194,7 +195,7 @@ class ChangeWorkItemStateService extends BaseWorkItemService { } meetFinalStateRequirementsOfCompleted() { - let performedProcedure = _.get(this.workItem.dicomJson, "00741216"); + let performedProcedure = _.get(this.workItemDicomJson.dicomJson, "00741216"); if (performedProcedure && _.get(performedProcedure, "Value.0.00404050") && _.get(performedProcedure, "Value.0.00404051")) { diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 866073ee..d988476f 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -5,18 +5,18 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { DicomJsonModel } = require("@dicom-json-model"); const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { SubscribeService } = require("@api/dicom-web/controller/UPS-RS/service/subscribe.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); class CreateWorkItemService extends BaseWorkItemService { constructor(req, res) { super(req, res); this.requestWorkItem = /** @type {Object[]} */(this.request.body).pop(); - /** @type {DicomJsonModel} */ - this.requestWorkItem = new DicomJsonModel(this.requestWorkItem); + /** @type {BaseDicomJson} */ + this.requestWorkItem = new BaseDicomJson(this.requestWorkItem); } async createUps() { @@ -74,7 +74,7 @@ class CreateWorkItemService extends BaseWorkItemService { } async triggerCreateEvent(workItem) { - let workItemDicomJson = new DicomJsonModel(workItem); + let workItemDicomJson = new BaseDicomJson(workItem); let hitGlobalSubscriptions = await this.getHitGlobalSubscriptions(workItemDicomJson); for (let hitGlobalSubscription of hitGlobalSubscriptions) { let subscribeService = new SubscribeService(this.request, this.response); @@ -84,11 +84,11 @@ class CreateWorkItemService extends BaseWorkItemService { await subscribeService.create(); } - let hitSubscriptions = await this.getHitSubscriptions(workItemDicomJson); + let hitSubscriptions = await this.getHitSubscriptions(workItem); if (hitSubscriptions) { let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItem.toDicomJsonModel()), hitSubscriptionAeTitleArray); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(await workItem.toDicomJson()), hitSubscriptionAeTitleArray); let assignedEventInformationArray = await this.getAssignedEventInformationArray( workItemDicomJson, _.get(workItemDicomJson.dicomJson, `${dictionary.keyword.ScheduledStationNameCodeSequence}`, false), diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index 92ca30cc..eb771c92 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -1,5 +1,4 @@ const _ = require("lodash"); -const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); @@ -12,6 +11,7 @@ const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/u const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); class SubscribeService extends BaseWorkItemService { @@ -37,9 +37,9 @@ class SubscribeService extends BaseWorkItemService { this.query = convertAllQueryToDicomTag(this.request.query); await this.createOrUpdateGlobalSubscription(); } else { - let workItem = await this.findOneWorkItem(this.upsInstanceUID); + let workItem = await WorkItemModel.findOneByUpsInstanceUID(this.upsInstanceUID); await this.createOrUpdateSubscription(workItem); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, this.upsInstanceUID, this.stateReportOf(workItem), [this.subscriberAeTitle]); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, this.upsInstanceUID, this.stateReportOf(await workItem.toDicomJson()), [this.subscriberAeTitle]); } await this.triggerUpsEvents(); @@ -58,11 +58,11 @@ class SubscribeService extends BaseWorkItemService { /** * - * @param {DicomJsonModel} workItem + * @param {any} workItem repository workItem * @returns */ async createOrUpdateSubscription(workItem) { - let subscription = await this.findOneSubscription(workItem); + let subscription = await this.findOneSubscription(); let subscribed = this.deletionLock ? SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK : SUBSCRIPTION_STATE.SUBSCRIBED_LOCK; await this.updateWorkItemSubscription(workItem, subscribed); if (!subscription) { @@ -70,7 +70,7 @@ class SubscribeService extends BaseWorkItemService { let subscriptionObj = new subscriptionModel({ aeTitle: this.subscriberAeTitle, workItems: [ - workItem.dicomJson._id + workItem._id ], isDeletionLock: this.deletionLock, subscribed: subscribed @@ -98,8 +98,8 @@ class SubscribeService extends BaseWorkItemService { } async updateWorkItemSubscription(workItem, subscription) { - workItem.dicomJson.subscribed = subscription; - await workItem.dicomJson.save(); + workItem.subscribed = subscription; + await workItem.save(); } //#endregion @@ -135,8 +135,8 @@ class SubscribeService extends BaseWorkItemService { let notSubscribedWorkItems = await this.findNotSubscribedWorkItems(); for(let notSubscribedWorkItem of notSubscribedWorkItems) { - let workItemDicomJson = new DicomJsonModel(notSubscribedWorkItem); - await this.createOrUpdateSubscription(workItemDicomJson); + let workItemDicomJson = await notSubscribedWorkItem.toDicomJson(); + await this.createOrUpdateSubscription(notSubscribedWorkItem); this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItemDicomJson), [this.subscriberAeTitle]); } diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 655ab405..00ff4466 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -1,7 +1,6 @@ const _ = require("lodash"); -const { DicomJsonModel } = require("@dicom-json-model"); const { DicomCode } = require("@models/DICOM/code"); -const workItemModel = require("@models/mongodb/models/workitems.model"); +const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); const subscriptionModel = require("@models/mongodb/models/upsSubscription"); const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); const { @@ -41,7 +40,7 @@ class UnSubscribeService extends BaseWorkItemService { await this.deleteGlobalSubscription(); } else { - let workItem = await this.findOneWorkItem(this.upsInstanceUID); + let workItem = await WorkItemModel.findOneByUpsInstanceUID(this.upsInstanceUID); if (!(await this.isSubscriptionExist())) { throw new DicomWebServiceError( @@ -58,16 +57,16 @@ class UnSubscribeService extends BaseWorkItemService { /** * - * @param {DicomJsonModel} workItem + * @param {any} workItem repository workItem */ async deleteSubscription(workItem) { await subscriptionModel.findOneAndUpdate({ aeTitle: this.subscriberAeTitle, - workItems: workItem.dicomJson._id + workItems: workItem._id }, { $pull: { - workItems: workItem.dicomJson._id + workItems: workItem._id } }); diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 2b3f1c6f..55f0a639 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -1,18 +1,15 @@ const _ = require("lodash"); -const workItemModel = require("@models/mongodb/models/workitems.model"); +const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { DicomJsonModel } = require("@dicom-json-model"); const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); - - - +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); class UpdateWorkItemService extends BaseWorkItemService { static notAllowedAttributes = Object.freeze([ @@ -38,32 +35,33 @@ class UpdateWorkItemService extends BaseWorkItemService { constructor(req, res) { super(req, res); this.requestWorkItem = /** @type {Object[]} */(this.request.body).pop(); - /** @type {DicomJsonModel} */ - this.requestWorkItem = new DicomJsonModel(this.requestWorkItem); - this.workItem = null; + /** @type { BaseDicomJson } */ + this.requestWorkItem = new BaseDicomJson(this.requestWorkItem); this.transactionUID = null; } async updateUps() { this.transactionUID = this.requestWorkItem.getString("00081195"); - this.workItem = await this.findOneWorkItem(this.request.params.workItem, true); + let workItem = await WorkItemModel.findOneByUpsInstanceUID(this.request.params.workItem); + /** @type { BaseDicomJson } */ + this.workItemDicomJson = await workItem.toDicomJson(); await this.checkRequestUpsIsValid(); this.adjustRequestWorkItem(); - let updatedWorkItem = await workItemModel.findOneAndUpdate({ - upsInstanceUID: this.workItem.dicomJson.upsInstanceUID + let updatedWorkItem = await WorkItemModel.findOneAndUpdate({ + upsInstanceUID: this.request.params.workItem }, { ...this.requestWorkItem.dicomJson }, { new: true }); - this.triggerUpdateWorkItemEvent(updatedWorkItem); + this.triggerUpdateWorkItemEvent(updatedWorkItem); } async triggerUpdateWorkItemEvent(workItem) { - let updateWorkItemDicomJson = new DicomJsonModel(workItem); - let hitSubscriptions = await this.getHitSubscriptions(updateWorkItemDicomJson); + let updateWorkItemDicomJson = await workItem.toDicomJson(); + let hitSubscriptions = await this.getHitSubscriptions(workItem); if (hitSubscriptions.length === 0) { return workItem; } @@ -72,11 +70,11 @@ class UpdateWorkItemService extends BaseWorkItemService { //Each time the SCP changes the Input Readiness State (0040,4041) Attribute for a UPS instance, the SCP shall send a UPS State Report Event to subscribed SCUs. let modifiedInputReadLineState = this.requestWorkItem.getString(`${dictionary.keyword.InputReadinessState}`); - let originalInputReadLineState = this.workItem.getString(`${dictionary.keyword.InputReadinessState}`); + let originalInputReadLineState = this.workItemDicomJson.getString(`${dictionary.keyword.InputReadinessState}`); if (modifiedInputReadLineState && modifiedInputReadLineState !== originalInputReadLineState) { this.addUpsEvent( UPS_EVENT_TYPE.StateReport, - this.workItem.dicomJson.upsInstanceUID, + this.request.params.workItem, this.stateReportOf(updateWorkItemDicomJson), hitSubscriptionAeTitleArray ); @@ -87,14 +85,14 @@ class UpdateWorkItemService extends BaseWorkItemService { this.triggerUpsEvents(); } - + addProgressInfoUpdatedEvent(workItemDicomJson, aeTitles) { let modifiedProcedureStepProgressInfo = _.get(this.requestWorkItem.dicomJson, dictionary.keyword.ProcedureStepProgressInformationSequence); - let originalProcedureStepProgressInfo = _.get(this.workItem.dicomJson , dictionary.keyword.ProcedureStepProgressInformationSequence); + let originalProcedureStepProgressInfo = _.get(this.workItemDicomJson.dicomJson, dictionary.keyword.ProcedureStepProgressInformationSequence); if (modifiedProcedureStepProgressInfo && !_.isEqual(modifiedProcedureStepProgressInfo, originalProcedureStepProgressInfo)) { this.addUpsEvent( UPS_EVENT_TYPE.ProgressReport, - this.workItem.dicomJson.upsInstanceUID, + this.request.params.workItem, this.progressReportOf(workItemDicomJson), aeTitles ); @@ -103,22 +101,22 @@ class UpdateWorkItemService extends BaseWorkItemService { addAssignedEvents(workItemDicomJson, aeTitles) { let modifiedPerformer = _.get(this.requestWorkItem.dicomJson, dictionary.keyword.ScheduledHumanPerformersSequence); - let originalPerformer = _.get(this.workItem.dicomJson, dictionary.keyword.ScheduledHumanPerformersSequence); + let originalPerformer = _.get(this.workItemDicomJson.dicomJson, dictionary.keyword.ScheduledHumanPerformersSequence); let performerUpdated = modifiedPerformer && !_.isEqual(modifiedPerformer, originalPerformer); let modifiedStationName = _.get(this.requestWorkItem.dicomJson, dictionary.keyword.ScheduledStationNameCodeSequence); - let originalStationName = _.get(this.workItem.dicomJson, dictionary.keyword.ScheduledStationNameCodeSequence); + let originalStationName = _.get(this.workItemDicomJson.dicomJson, dictionary.keyword.ScheduledStationNameCodeSequence); let stationNameUpdate = modifiedStationName && !_.isEqual(modifiedStationName, originalStationName); let assignedEventInformationArray = this.getAssignedEventInformationArray(workItemDicomJson, performerUpdated, stationNameUpdate); - for(let assignedEventInfo of assignedEventInformationArray) { + for (let assignedEventInfo of assignedEventInformationArray) { this.addUpsEvent(UPS_EVENT_TYPE.Assigned, workItemDicomJson.dicomJson.upsInstanceUID, assignedEventInfo, aeTitles); } } checkRequestUpsIsValid() { - let procedureState = this.workItem.getString("00741000"); + let procedureState = this.workItemDicomJson.getString("00741000"); const mappingMethod = { "SCHEDULED": () => { @@ -131,7 +129,7 @@ class UpdateWorkItemService extends BaseWorkItemService { } }, "IN PROGRESS": () => { - let foundUpsTransactionUID = this.workItem.getString("00081195"); + let foundUpsTransactionUID = this.workItemDicomJson.getString("00081195"); if (!this.transactionUID) { throw new DicomWebServiceError( DicomWebStatusCodes.UPSTransactionUIDNotCorrect, diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 9f995435..5c137dee 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -68,6 +68,16 @@ class BaseDicomJson { return _.get(this.dicomJson, `${tag}.Value.0`, undefined); } + /** + * + * @param {string} tag + */ + getString(tag) { + let tagSplit = tag.split("."); + let seqTag = tagSplit.join(".Value.0."); + return String(_.get(this.dicomJson, `${seqTag}.Value.0`, "")); + } + getSequenceItem(tag) { return JSONPath({ path: `$..${tag}`, @@ -279,14 +289,6 @@ class DicomJsonModel { } } - /** - * - * @param {string} tag - */ - getString(tag) { - return String(_.get(this.dicomJson, `${tag}.Value.0`, "")); - } - getMediaStorageInfo() { return { "00880130": { diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index 5722e0c0..dea5fbec 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -4,7 +4,7 @@ const _ = require("lodash"); const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { SUBSCRIPTION_STATE } = require("../../DICOM/ups"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); const { PatientModel } = require("./patient.model"); let workItemSchema = new mongoose.Schema( @@ -50,7 +50,7 @@ let workItemSchema = new mongoose.Schema( } } ] - + }) || []; }, /** @@ -78,8 +78,20 @@ let workItemSchema = new mongoose.Schema( } }, methods: { - toDicomJsonModel: function () { - return new DicomJsonModel(this); + toDicomJson: async function () { + return new BaseDicomJson(await this.toGeneralDicomJson()); + }, + toGeneralDicomJson: async function () { + let obj = this.toObject(); + + delete obj._id; + delete obj.id; + delete obj.upsInstanceUID; + delete obj.patientID; + delete obj.transactionUID; + delete obj.subscribed; + + return obj; } } } @@ -126,7 +138,7 @@ workItemSchema.statics.getDicomJson = async function (queryOptions) { }) .exec(); - + let workItemDicomJson = docs.map((v) => { let obj = v.toObject(); delete obj._id; From 27421856cb7bfdb60fbdb3b0dcd18808b4c54e8e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 19:00:02 +0800 Subject: [PATCH 311/365] refactor: wrap `findByWorkItem` for subscriptionModel --- .../UPS-RS/service/base-workItem.service.js | 39 ++++--------------- models/mongodb/models/upsSubscription.js | 5 +++ 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 7f03a3f2..7a590dfa 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -1,6 +1,5 @@ const _ = require("lodash"); const { WorkItemEvent } = require("./workItem-event"); -const { DicomJsonModel } = require("@dicom-json-model"); const { findWsArrayByAeTitle } = require("@root/websocket"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); @@ -9,6 +8,7 @@ const subscriptionModel = require("@models/mongodb/models/upsSubscription"); const { WorkItemModel } = require("@dbModels/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); class BaseWorkItemService { constructor(req, res) { @@ -18,8 +18,6 @@ class BaseWorkItemService { this.response = res; /** @type {WorkItemEvent[]} */ this.upsEvents = []; - /** @type {DicomJsonModel} */ - this.workItem = null; } addUpsEvent(type, upsInstanceUID, eventInformation, subscribers) { @@ -49,7 +47,7 @@ class BaseWorkItemService { /** * Use for getting event information - * @param {DicomJsonModel} workItem + * @param {BaseDicomJson} workItem */ stateReportOf(workItem) { let eventInformation = { @@ -126,10 +124,13 @@ class BaseWorkItemService { return hitGlobalSubscriptions; } + /** + * + * @param {any} workItem repository workItem + * @returns + */ async getHitSubscriptions(workItem) { - let hitSubscriptions = await subscriptionModel.find({ - workItems: workItem.dicomJson._id - }); + let hitSubscriptions = await subscriptionModel.findByWorkItem(workItem); return hitSubscriptions; } @@ -160,30 +161,6 @@ class BaseWorkItemService { } return eventInformation; } - - /** - * - * @param {string} upsInstanceUID - * @returns - */ - async findOneWorkItem(upsInstanceUID, toObject=false) { - let workItem = await WorkItemModel.findOne({ - upsInstanceUID: upsInstanceUID - }); - - if (!workItem) { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - if (toObject) { - workItem = workItem.toObject(); - } - - return new DicomJsonModel(workItem); - } } module.exports.BaseWorkItemService = BaseWorkItemService; \ No newline at end of file diff --git a/models/mongodb/models/upsSubscription.js b/models/mongodb/models/upsSubscription.js index 2d271b8a..30f1ec41 100644 --- a/models/mongodb/models/upsSubscription.js +++ b/models/mongodb/models/upsSubscription.js @@ -31,6 +31,11 @@ let upsSubscriptionSchema = new mongoose.Schema( versionKey: false, toObject: { getters: true + }, + statics: { + findByWorkItem: async function(workItem) { + return await mongoose.model("upsSubscription").find({workItems: workItem._id}).exec(); + } } } ); From e888afb07bf93e9211632ac944a432181a465cd4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 19:22:33 +0800 Subject: [PATCH 312/365] refactor: wrap methods for `Ups(global)/subscriptionModel` - `getCursor` for `UpsGlobalSubscriptionModel` - `findOneByAeTitle` for `UpsSubscriptionModel` - `getHitGlobalSubscriptions` input arg use workItem instead of dicomJsonModel --- .../UPS-RS/service/base-workItem.service.js | 16 +++++++--------- .../UPS-RS/service/create-workItem.service.js | 4 ++-- models/mongodb/models/upsGlobalSubscription.js | 6 ++++++ models/mongodb/models/upsSubscription.js | 9 +++++++++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 7a590dfa..e21705ed 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -3,8 +3,8 @@ const { WorkItemEvent } = require("./workItem-event"); const { findWsArrayByAeTitle } = require("@root/websocket"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); -const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); -const subscriptionModel = require("@models/mongodb/models/upsSubscription"); +const { UpsGlobalSubscriptionModel } = require("@models/mongodb/models/upsGlobalSubscription"); +const { UpsSubscriptionModel } = require("@models/mongodb/models/upsSubscription"); const { WorkItemModel } = require("@dbModels/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); @@ -83,9 +83,7 @@ class BaseWorkItemService { } async isAeTileSubscribed(aeTitle) { - let subscription = await subscriptionModel.findOne({ - aeTitle: aeTitle - }); + let subscription = await UpsSubscriptionModel.findOneByAeTitle(aeTitle); if (!subscription) return false; @@ -95,11 +93,11 @@ class BaseWorkItemService { } async getGlobalSubscriptionsCursor() { - return globalSubscriptionModel.find({}).cursor(); + return await UpsGlobalSubscriptionModel.getCursor({}); } /** - * @param {DicomJsonModel} workItem + * @param {any} workItem repository workItem */ async getHitGlobalSubscriptions(workItem) { let globalSubscriptionsCursor = await this.getGlobalSubscriptionsCursor(); @@ -111,7 +109,7 @@ class BaseWorkItemService { } else { let { $match } = await convertRequestQueryToMongoQuery(globalSubscription.queryKeys); $match.$and.push({ - upsInstanceUID: workItem.dicomJson.upsInstanceUID + upsInstanceUID: workItem.upsInstanceUID }); let count = await WorkItemModel.countDocuments({ ...$match @@ -130,7 +128,7 @@ class BaseWorkItemService { * @returns */ async getHitSubscriptions(workItem) { - let hitSubscriptions = await subscriptionModel.findByWorkItem(workItem); + let hitSubscriptions = await UpsSubscriptionModel.findByWorkItem(workItem); return hitSubscriptions; } diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index d988476f..aebabedd 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -74,8 +74,8 @@ class CreateWorkItemService extends BaseWorkItemService { } async triggerCreateEvent(workItem) { - let workItemDicomJson = new BaseDicomJson(workItem); - let hitGlobalSubscriptions = await this.getHitGlobalSubscriptions(workItemDicomJson); + let workItemDicomJson = await workItem.toDicomJson(); + let hitGlobalSubscriptions = await this.getHitGlobalSubscriptions(workItem); for (let hitGlobalSubscription of hitGlobalSubscriptions) { let subscribeService = new SubscribeService(this.request, this.response); subscribeService.upsInstanceUID = workItemDicomJson.dicomJson.upsInstanceUID; diff --git a/models/mongodb/models/upsGlobalSubscription.js b/models/mongodb/models/upsGlobalSubscription.js index b81ec179..a7a3838e 100644 --- a/models/mongodb/models/upsGlobalSubscription.js +++ b/models/mongodb/models/upsGlobalSubscription.js @@ -31,6 +31,11 @@ let upsGlobalSubscriptionSchema = new mongoose.Schema( versionKey: false, toObject: { getters: true + }, + statics: { + getCursor: async function (query, options) { + return await mongoose.model("upsGlobalSubscription").find(query, options).cursor(); + } } } ); @@ -43,3 +48,4 @@ let upsSubscriptionModel = mongoose.model( ); module.exports = upsSubscriptionModel; +module.exports.UpsGlobalSubscriptionModel = upsSubscriptionModel; diff --git a/models/mongodb/models/upsSubscription.js b/models/mongodb/models/upsSubscription.js index 30f1ec41..ae4d8a00 100644 --- a/models/mongodb/models/upsSubscription.js +++ b/models/mongodb/models/upsSubscription.js @@ -35,6 +35,14 @@ let upsSubscriptionSchema = new mongoose.Schema( statics: { findByWorkItem: async function(workItem) { return await mongoose.model("upsSubscription").find({workItems: workItem._id}).exec(); + }, + /** + * + * @param {string} aeTitle + * @returns repository item + */ + findOneByAeTitle: async function(aeTitle) { + return await mongoose.model("upsSubscription").findOne({ aeTitle }).exec(); } } } @@ -48,3 +56,4 @@ let upsSubscriptionModel = mongoose.model( ); module.exports = upsSubscriptionModel; +module.exports.UpsSubscriptionModel = upsSubscriptionModel; From 7c15c59649d53fb698406c126595d82a7e6a9ee2 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 19:53:33 +0800 Subject: [PATCH 313/365] refactor: wrap `getCountWithQueryAndUpsInstanceUID` for work item model --- .../UPS-RS/service/base-workItem.service.js | 8 +------- models/mongodb/models/workitems.model.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index e21705ed..cf3b9ff9 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -107,13 +107,7 @@ class BaseWorkItemService { if (!globalSubscription.queryKeys) { hitGlobalSubscriptions.push(globalSubscription); } else { - let { $match } = await convertRequestQueryToMongoQuery(globalSubscription.queryKeys); - $match.$and.push({ - upsInstanceUID: workItem.upsInstanceUID - }); - let count = await WorkItemModel.countDocuments({ - ...$match - }); + let count = await WorkItemModel.getCountWithQueryAndUpsInstanceUID(globalSubscription.queryKeys, workItem.upsInstanceUID); if (count > 0) hitGlobalSubscriptions.push(globalSubscription); } diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index dea5fbec..85d8b94d 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -6,6 +6,7 @@ const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { SUBSCRIPTION_STATE } = require("../../DICOM/ups"); const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); const { PatientModel } = require("./patient.model"); +const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); let workItemSchema = new mongoose.Schema( { @@ -75,6 +76,22 @@ let workItemSchema = new mongoose.Schema( return await mongoose.model("workItems").findOne({ upsInstanceUID }); + }, + /** + * + * @param {Object} query the query structure example { "00100010.Value": "foo" } or { "00100010.Value.00100010.Value": "bar" } + * @param {string} upsInstanceUID + * @returns {number} count + */ + async getCountWithQueryAndUpsInstanceUID(query, upsInstanceUID) { + let { $match } = await convertRequestQueryToMongoQuery(query); + $match.$and.push({ + upsInstanceUID: upsInstanceUID + }); + return await mongoose.model("workItems").countDocuments({ + ...$match + }); + } }, methods: { From 3592f101efa540391f49d70b13b1d18f38e8fb02 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 19:55:24 +0800 Subject: [PATCH 314/365] fix: missing update work item dicom json usage - The dicom json only have general dicom json model's info - change all dicomJson.upsInstanceUID using repository item.upsInstanceUID --- .../UPS-RS/service/change-workItem-state.service.js | 2 +- .../controller/UPS-RS/service/create-workItem.service.js | 6 +++--- .../controller/UPS-RS/service/subscribe.service.js | 4 ++-- .../controller/UPS-RS/service/update-workItem.service.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index ff1719c3..2711f3d0 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -68,7 +68,7 @@ class ChangeWorkItemStateService extends BaseWorkItemService { let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, updatedWorkItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(updatedWorkItemDicomJson), hitSubscriptionAeTitleArray); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, updatedWorkItem.upsInstanceUID, this.stateReportOf(updatedWorkItemDicomJson), hitSubscriptionAeTitleArray); this.triggerUpsEvents(); } diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index aebabedd..75e1c314 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -78,7 +78,7 @@ class CreateWorkItemService extends BaseWorkItemService { let hitGlobalSubscriptions = await this.getHitGlobalSubscriptions(workItem); for (let hitGlobalSubscription of hitGlobalSubscriptions) { let subscribeService = new SubscribeService(this.request, this.response); - subscribeService.upsInstanceUID = workItemDicomJson.dicomJson.upsInstanceUID; + subscribeService.upsInstanceUID = workItem.upsInstanceUID; subscribeService.deletionLock = hitGlobalSubscription.isDeletionLock; subscribeService.subscriberAeTitle = hitGlobalSubscription.aeTitle; await subscribeService.create(); @@ -88,7 +88,7 @@ class CreateWorkItemService extends BaseWorkItemService { if (hitSubscriptions) { let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(await workItem.toDicomJson()), hitSubscriptionAeTitleArray); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItem.upsInstanceUID, this.stateReportOf(await workItem.toDicomJson()), hitSubscriptionAeTitleArray); let assignedEventInformationArray = await this.getAssignedEventInformationArray( workItemDicomJson, _.get(workItemDicomJson.dicomJson, `${dictionary.keyword.ScheduledStationNameCodeSequence}`, false), @@ -96,7 +96,7 @@ class CreateWorkItemService extends BaseWorkItemService { ); for (let assignedEventInfo of assignedEventInformationArray) { - this.addUpsEvent(UPS_EVENT_TYPE.Assigned, workItemDicomJson.dicomJson.upsInstanceUID, assignedEventInfo, hitSubscriptionAeTitleArray); + this.addUpsEvent(UPS_EVENT_TYPE.Assigned, workItem.upsInstanceUID, assignedEventInfo, hitSubscriptionAeTitleArray); } } diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index eb771c92..8a442b33 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -88,7 +88,7 @@ class SubscribeService extends BaseWorkItemService { subscribed: subscribed }, $addToSet: { - workItems: workItem.dicomJson._id + workItems: workItem._id } }); subscription.isDeletionLock = this.deletionLock; @@ -138,7 +138,7 @@ class SubscribeService extends BaseWorkItemService { let workItemDicomJson = await notSubscribedWorkItem.toDicomJson(); await this.createOrUpdateSubscription(notSubscribedWorkItem); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItemDicomJson.dicomJson.upsInstanceUID, this.stateReportOf(workItemDicomJson), [this.subscriberAeTitle]); + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, notSubscribedWorkItem.upsInstanceUID, this.stateReportOf(workItemDicomJson), [this.subscriberAeTitle]); } } diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 55f0a639..26fd03f6 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -110,7 +110,7 @@ class UpdateWorkItemService extends BaseWorkItemService { let assignedEventInformationArray = this.getAssignedEventInformationArray(workItemDicomJson, performerUpdated, stationNameUpdate); for (let assignedEventInfo of assignedEventInformationArray) { - this.addUpsEvent(UPS_EVENT_TYPE.Assigned, workItemDicomJson.dicomJson.upsInstanceUID, assignedEventInfo, aeTitles); + this.addUpsEvent(UPS_EVENT_TYPE.Assigned, this.request.params.workItem, assignedEventInfo, aeTitles); } } From abd0de2b1ce016640e9c07fe737e486600a1c406 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 20:58:02 +0800 Subject: [PATCH 315/365] refactor: wrap find/create/update methods for `ups(global)subscriptionModel` --- .../UPS-RS/service/subscribe.service.js | 64 +++++-------------- .../mongodb/models/upsGlobalSubscription.js | 12 ++++ models/mongodb/models/upsSubscription.js | 22 +++++++ 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index 8a442b33..f80f134c 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -1,8 +1,7 @@ const _ = require("lodash"); -const { DicomCode } = require("@models/DICOM/code"); const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); -const subscriptionModel = require("@models/mongodb/models/upsSubscription"); -const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); +const { UpsSubscriptionModel } = require("@models/mongodb/models/upsSubscription"); +const { UpsGlobalSubscriptionModel } = require("@models/mongodb/models/upsGlobalSubscription"); const { DicomWebServiceError, DicomWebStatusCodes @@ -11,7 +10,6 @@ const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/u const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/service/base-workItem.service"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); -const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); class SubscribeService extends BaseWorkItemService { @@ -30,8 +28,8 @@ class SubscribeService extends BaseWorkItemService { } async create() { - - if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID || + + if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID || this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID) { this.query = convertAllQueryToDicomTag(this.request.query); @@ -41,16 +39,14 @@ class SubscribeService extends BaseWorkItemService { await this.createOrUpdateSubscription(workItem); this.addUpsEvent(UPS_EVENT_TYPE.StateReport, this.upsInstanceUID, this.stateReportOf(await workItem.toDicomJson()), [this.subscriberAeTitle]); } - + await this.triggerUpsEvents(); } //#region Subscription async findOneSubscription() { - - let subscription = await subscriptionModel.findOne({ - aeTitle: this.subscriberAeTitle - }); + + let subscription = await UpsSubscriptionModel.findOneByAeTitle(this.subscriberAeTitle); return subscription; @@ -67,33 +63,10 @@ class SubscribeService extends BaseWorkItemService { await this.updateWorkItemSubscription(workItem, subscribed); if (!subscription) { // Create - let subscriptionObj = new subscriptionModel({ - aeTitle: this.subscriberAeTitle, - workItems: [ - workItem._id - ], - isDeletionLock: this.deletionLock, - subscribed: subscribed - }); - - let createdSubscription = await subscriptionObj.save(); - return createdSubscription; + return await UpsSubscriptionModel.createSubscriptionForWorkItem(workItem, this.subscriberAeTitle, this.deletionLock, subscribed); } else { // Update - let updatedSubscription =await subscriptionModel.findOneAndUpdate({ - _id: subscription._id - }, { - $set: { - isDeletionLock: this.deletionLock, - subscribed: subscribed - }, - $addToSet: { - workItems: workItem._id - } - }); - subscription.isDeletionLock = this.deletionLock; - subscription.subscribed = subscribed; - return updatedSubscription; + return await UpsSubscriptionModel.updateSubscription(subscription, workItem, this.deletionLock, subscribed); } } @@ -117,36 +90,29 @@ class SubscribeService extends BaseWorkItemService { } if (!subscription) { //Create - let subscriptionObj = new globalSubscriptionModel({ + await UpsGlobalSubscriptionModel.createGlobalSubscription({ aeTitle: this.subscriberAeTitle, isDeletionLock: this.deletionLock, subscribed: subscribed, queryKeys: this.query }); - - let createdSubscription = await subscriptionObj.save(); } else { //Update - subscription.isDeletionLock = this.deletionLock; - subscription.subscribed = subscribed; - subscription.queryKeys = this.query; - await subscription.save(); + await UpsGlobalSubscriptionModel.updateRepositoryInstance(subscription, this.query, this.deletionLock, subscribed); } let notSubscribedWorkItems = await this.findNotSubscribedWorkItems(); - for(let notSubscribedWorkItem of notSubscribedWorkItems) { + for (let notSubscribedWorkItem of notSubscribedWorkItems) { let workItemDicomJson = await notSubscribedWorkItem.toDicomJson(); await this.createOrUpdateSubscription(notSubscribedWorkItem); - + this.addUpsEvent(UPS_EVENT_TYPE.StateReport, notSubscribedWorkItem.upsInstanceUID, this.stateReportOf(workItemDicomJson), [this.subscriberAeTitle]); } } async findOneGlobalSubscription() { - - let globalSubscription = await globalSubscriptionModel.findOne({ - aeTitle: this.subscriberAeTitle - }); + + let globalSubscription = await UpsGlobalSubscriptionModel.findOneByAeTitle(this.subscriberAeTitle); return globalSubscription; diff --git a/models/mongodb/models/upsGlobalSubscription.js b/models/mongodb/models/upsGlobalSubscription.js index a7a3838e..424a3b8c 100644 --- a/models/mongodb/models/upsGlobalSubscription.js +++ b/models/mongodb/models/upsGlobalSubscription.js @@ -35,6 +35,18 @@ let upsGlobalSubscriptionSchema = new mongoose.Schema( statics: { getCursor: async function (query, options) { return await mongoose.model("upsGlobalSubscription").find(query, options).cursor(); + }, + createGlobalSubscription: async function(globalSubscription) { + return await mongoose.model("upsGlobalSubscription").create(globalSubscription); + }, + updateRepositoryInstance: async function(globalSubscription, query, deletionLock ,subscribed) { + globalSubscription.isDeletionLock = deletionLock; + globalSubscription.subscribed = subscribed; + globalSubscription.queryKeys = query; + return await globalSubscription.save(); + }, + findOneByAeTitle: async function(aeTitle) { + return await mongoose.model("upsGlobalSubscription").findOne({aeTitle: aeTitle}); } } } diff --git a/models/mongodb/models/upsSubscription.js b/models/mongodb/models/upsSubscription.js index ae4d8a00..6fb372dd 100644 --- a/models/mongodb/models/upsSubscription.js +++ b/models/mongodb/models/upsSubscription.js @@ -43,6 +43,28 @@ let upsSubscriptionSchema = new mongoose.Schema( */ findOneByAeTitle: async function(aeTitle) { return await mongoose.model("upsSubscription").findOne({ aeTitle }).exec(); + }, + createSubscriptionForWorkItem: async function(workItem, aeTitle, deletionLock, subscribed) { + let subscription = new mongoose.model("upsSubscription")({ + aeTitle: aeTitle, + workItems: [workItem._id], + subscribed: subscribed, + isDeletionLock: deletionLock + }); + return await subscription.save(); + }, + updateSubscription: async function(subscription, workItem, deletionLock, subscribed) { + return await mongoose.model("upsSubscription").findOneAndUpdate({ + _id: subscription._id + }, { + $set: { + isDeletionLock: deletionLock, + subscribed: subscribed + }, + $addToSet: { + workItems: workItem._id + } + }); } } } From 3c3487fb1eebe8a5387c3e8fca3f64eeb43096d0 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 17 Jan 2024 21:10:23 +0800 Subject: [PATCH 316/365] refactor: use `@dbModels` instead of `@model/mongodb...` --- .../service/change-filtered-mwlItem-status.js | 2 +- .../MWL-RS/service/change-mwlItem-status.js | 2 +- .../MWL-RS/service/count-mwlItem.service.js | 2 +- .../MWL-RS/service/create-mwlItem.service.js | 6 +++--- .../MWL-RS/service/get-mwlItem.service.js | 2 +- .../PAM-RS/service/create-patient.service.js | 2 +- .../PAM-RS/service/delete-patient.service.js | 2 +- .../PAM-RS/service/update-patient.service.js | 2 +- .../UPS-RS/service/base-workItem.service.js | 4 ++-- .../controller/UPS-RS/service/cancel.service.js | 2 +- .../service/change-workItem-state.service.js | 2 +- .../UPS-RS/service/get-workItem.service.js | 4 ++-- .../UPS-RS/service/subscribe.service.js | 6 +++--- .../service/suspend-subscription.service.js | 6 +++--- .../UPS-RS/service/unsubscribe.service.js | 16 ++++++++-------- .../UPS-RS/service/update-workItem.service.js | 2 +- dimse/mwlQueryTask.js | 2 +- models/DICOM/audit/auditManager.js | 2 +- 18 files changed, 33 insertions(+), 33 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js index 4711b742..8db68d70 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); diff --git a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js index e0e0ba2c..5bd8373c 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); diff --git a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js index 449059be..a2f338fa 100644 --- a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js @@ -1,4 +1,4 @@ -const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js index 49cd8dd9..1fa4910b 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); -const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); -const { PatientModel } = require("@models/mongodb/models/patient.model"); -const { StudyModel } = require("@models/mongodb/models/study.model"); +const { MwlItemModel } = require("@dbModels/mwlitems.model"); +const { PatientModel } = require("@dbModels/patient.model"); +const { StudyModel } = require("@dbModels/study.model"); const crypto = require('crypto'); const moment = require("moment"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); diff --git a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js index e277b18e..7f8cc8cd 100644 --- a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js @@ -1,4 +1,4 @@ -const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); diff --git a/api/dicom-web/controller/PAM-RS/service/create-patient.service.js b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js index 7043ca93..6d87dd9c 100644 --- a/api/dicom-web/controller/PAM-RS/service/create-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js @@ -1,4 +1,4 @@ -const { PatientModel } = require("@models/mongodb/models/patient.model"); +const { PatientModel } = require("@dbModels/patient.model"); const { set, get } = require("lodash"); const shortHash = require("shorthash2"); const { v4: uuidV4 } = require("uuid"); diff --git a/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js b/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js index 58aa2c01..e6ad0cd3 100644 --- a/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js @@ -1,5 +1,5 @@ const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { PatientModel } = require("@models/mongodb/models/patient.model"); +const { PatientModel } = require("@dbModels/patient.model"); class DeletePatientService { constructor(req, res) { diff --git a/api/dicom-web/controller/PAM-RS/service/update-patient.service.js b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js index d4e082a6..fdaa1f85 100644 --- a/api/dicom-web/controller/PAM-RS/service/update-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js @@ -1,5 +1,5 @@ const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { PatientModel } = require("@models/mongodb/models/patient.model"); +const { PatientModel } = require("@dbModels/patient.model"); const { set } = require("lodash"); diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index cf3b9ff9..2c8c020b 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -3,8 +3,8 @@ const { WorkItemEvent } = require("./workItem-event"); const { findWsArrayByAeTitle } = require("@root/websocket"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); -const { UpsGlobalSubscriptionModel } = require("@models/mongodb/models/upsGlobalSubscription"); -const { UpsSubscriptionModel } = require("@models/mongodb/models/upsSubscription"); +const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription"); +const { UpsSubscriptionModel } = require("@dbModels/upsSubscription"); const { WorkItemModel } = require("@dbModels/workitems.model"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index 4f69b80c..05a53537 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -8,7 +8,7 @@ const { BaseWorkItemService } = require("@api/dicom-web/controller/UPS-RS/servic const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { UPS_EVENT_TYPE } = require("./workItem-event"); const { raccoonConfig } = require("@root/config-class"); -const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); class CancelWorkItemService extends BaseWorkItemService { diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index 2711f3d0..b422f73c 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const moment = require("moment"); const { DicomCode } = require("@models/DICOM/code"); -const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { DicomWebServiceError, DicomWebStatusCodes diff --git a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 3906c345..1b90158d 100644 --- a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const workItemsModel = require("@models/mongodb/models/workitems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); @@ -37,7 +37,7 @@ class GetWorkItemService { requestParams: this.request.params }; - let docs = await workItemsModel.getDicomJson(queryOptions); + let docs = await WorkItemModel.getDicomJson(queryOptions); return this.adjustDocs(docs); } diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index f80f134c..4e3b67e9 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -1,7 +1,7 @@ const _ = require("lodash"); -const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); -const { UpsSubscriptionModel } = require("@models/mongodb/models/upsSubscription"); -const { UpsGlobalSubscriptionModel } = require("@models/mongodb/models/upsGlobalSubscription"); +const { WorkItemModel } = require("@dbModels/workitems.model"); +const { UpsSubscriptionModel } = require("@dbModels/upsSubscription"); +const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription"); const { DicomWebServiceError, DicomWebStatusCodes diff --git a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js index f47e5c0c..4ac7186f 100644 --- a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js +++ b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); +const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription"); const { DicomWebServiceError, DicomWebStatusCodes @@ -36,14 +36,14 @@ class SuspendSubscribeService extends BaseWorkItemService { async deleteGlobalSubscription() { - await globalSubscriptionModel.findOneAndDelete({ + await UpsGlobalSubscriptionModel.findOneAndDelete({ aeTitle: this.subscriberAeTitle }); } async isGlobalSubscriptionExist() { - return await globalSubscriptionModel.countDocuments({ + return await UpsGlobalSubscriptionModel.countDocuments({ aeTitle: this.subscriberAeTitle }) > 0; } diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 00ff4466..4cb02b72 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -1,8 +1,8 @@ const _ = require("lodash"); const { DicomCode } = require("@models/DICOM/code"); -const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); -const subscriptionModel = require("@models/mongodb/models/upsSubscription"); -const globalSubscriptionModel = require("@models/mongodb/models/upsGlobalSubscription"); +const { WorkItemModel } = require("@dbModels/workitems.model"); +const { UpsSubscriptionModel } = require("@dbModels/upsSubscription"); +const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription"); const { DicomWebServiceError, DicomWebStatusCodes @@ -61,7 +61,7 @@ class UnSubscribeService extends BaseWorkItemService { */ async deleteSubscription(workItem) { - await subscriptionModel.findOneAndUpdate({ + await UpsSubscriptionModel.findOneAndUpdate({ aeTitle: this.subscriberAeTitle, workItems: workItem._id }, { @@ -75,10 +75,10 @@ class UnSubscribeService extends BaseWorkItemService { async deleteGlobalSubscription() { await Promise.all([ - subscriptionModel.findOneAndDelete({ + UpsSubscriptionModel.findOneAndDelete({ aeTitle: this.subscriberAeTitle }), - globalSubscriptionModel.findOneAndDelete({ + UpsGlobalSubscriptionModel.findOneAndDelete({ aeTitle: this.subscriberAeTitle }) ]); @@ -86,13 +86,13 @@ class UnSubscribeService extends BaseWorkItemService { } async isSubscriptionExist() { - return await subscriptionModel.countDocuments({ + return await UpsSubscriptionModel.countDocuments({ aeTitle: this.subscriberAeTitle }) > 0; } async isGlobalSubscriptionExist() { - return await globalSubscriptionModel.countDocuments({ + return await UpsGlobalSubscriptionModel.countDocuments({ aeTitle: this.subscriberAeTitle }) > 0; } diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index 26fd03f6..f48227bd 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -1,5 +1,5 @@ const _ = require("lodash"); -const { WorkItemModel } = require("@models/mongodb/models/workitems.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { PatientModel } = require("@dbModels/patient.model"); const { UIDUtils } = require("@dcm4che/util/UIDUtils"); const { diff --git a/dimse/mwlQueryTask.js b/dimse/mwlQueryTask.js index c3708143..ec63c961 100644 --- a/dimse/mwlQueryTask.js +++ b/dimse/mwlQueryTask.js @@ -10,7 +10,7 @@ const { logger } = require("@root/utils/logs/log"); const { UID } = require("@dcm4che/data/UID"); const { QueryTaskUtils } = require("./utils"); const { default: BasicModQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModQueryTask"); -const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); +const { MwlItemModel } = require("@dbModels/mwlitems.model"); class JsMwlQueryTask { diff --git a/models/DICOM/audit/auditManager.js b/models/DICOM/audit/auditManager.js index d50fbcae..113de7e7 100644 --- a/models/DICOM/audit/auditManager.js +++ b/models/DICOM/audit/auditManager.js @@ -4,7 +4,7 @@ const { AuditMessageFactory } = require("./auditMessageFactory"); const { EventType } = require("./eventType"); const { AuditMessageModel } = require("@models/db/auditMessage.model"); const { AuditMessageModelLoggerDbImpl } = require("@models/db/auditMessage.loggerImpl"); -const AuditMessageModelMongodbDbImpl = require("@models/mongodb/models/auditMessage"); +const AuditMessageModelMongodbDbImpl = require("@dbModels/auditMessage"); const { raccoonConfig } = require("@root/config-class"); /** From b566525d3ec8ae24991ca5515ca5512093291914 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 14:56:42 +0800 Subject: [PATCH 317/365] refactor: wrap `createOrUpdatePatient` for `PatientModel` --- .../PAM-RS/service/create-patient.service.js | 14 +++----------- models/mongodb/models/patient.model.js | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/api/dicom-web/controller/PAM-RS/service/create-patient.service.js b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js index 6d87dd9c..1d4f5cb2 100644 --- a/api/dicom-web/controller/PAM-RS/service/create-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/create-patient.service.js @@ -1,7 +1,5 @@ const { PatientModel } = require("@dbModels/patient.model"); const { set, get } = require("lodash"); -const shortHash = require("shorthash2"); -const { v4: uuidV4 } = require("uuid"); class CreatePatientService { /** @@ -16,16 +14,10 @@ class CreatePatientService { async create() { let incomingPatient = this.request.body; - let patientID = shortHash(uuidV4()); + let patientID = get(incomingPatient, "00100020.Value.0"); set(incomingPatient, "patientID", patientID); - set(incomingPatient, "00100020.Value", [ - patientID - ]); - const patient = new PatientModel(incomingPatient); - await patient.save(); - return { - patientID - }; + let patient = await PatientModel.createOrUpdatePatient(patientID, incomingPatient); + return patient.toGeneralDicomJson(); } } diff --git a/models/mongodb/models/patient.model.js b/models/mongodb/models/patient.model.js index a8005304..c7045a86 100644 --- a/models/mongodb/models/patient.model.js +++ b/models/mongodb/models/patient.model.js @@ -8,12 +8,13 @@ const { const { raccoonConfig } = require("@root/config-class"); const { DicomSchemaOptionsFactory, PatientDocDicomJsonHandler } = require("../schema/dicom.schema"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); let patientSchemaOptions = _.merge( DicomSchemaOptionsFactory.get("patient", PatientDocDicomJsonHandler), { methods: { - toDicomJson: function() { + toGeneralDicomJson: async function () { let obj = this.toObject(); delete obj._id; delete obj.id; @@ -24,6 +25,9 @@ let patientSchemaOptions = _.merge( delete obj.updatedAt; return obj; + }, + toDicomJson: async function() { + return new BaseDicomJson(await this.toGeneralDicomJson()); } }, statics: { @@ -65,6 +69,16 @@ let patientSchemaOptions = _.merge( } return patient; + }, + /** + * + * @param {string} patientID + * @param {any} patient patient general dicom json + */ + createOrUpdatePatient: async function(patientID, patient) { + return await mongoose.model("patient").findOneAndUpdate({ + patientID + }, patient, { upsert: true, new: true }); } } } From b29975b49b9b58617a568a61002dc1b947705981 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 15:01:40 +0800 Subject: [PATCH 318/365] refactor: rename to `toGeneralDicomJson` for `toDicomJson` --- .../controller/MWL-RS/service/change-mwlItem-status.js | 2 +- .../controller/MWL-RS/service/create-mwlItem.service.js | 4 ++-- api/dicom-web/controller/PAM-RS/update-patient.js | 2 +- models/mongodb/models/mwlitems.model.js | 6 +++++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js index 5bd8373c..051ae76b 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js @@ -21,7 +21,7 @@ class ChangeMwlItemStatusService { _.set(mwlItem, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); await mwlItem.save(); - return mwlItem.toDicomJson(); + return mwlItem.toGeneralDicomJson(); } async getMwlItemByStudyUIDAndSpsID() { diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js index 1fa4910b..a4fa2417 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js @@ -124,7 +124,7 @@ class CreateMwlItemService { let mwlItemModelObj = new MwlItemModel(mwlDicomJson.dicomJson); await mwlItemModelObj.save(); this.apiLogger.logger.info(`create mwl item: ${studyInstanceUID}`); - return mwlItemModelObj.toDicomJson(); + return mwlItemModelObj.toGeneralDicomJson(); } else { // update foundMwl.$set({ @@ -132,7 +132,7 @@ class CreateMwlItemService { }); await foundMwl.save(); this.apiLogger.logger.info(`update mwl item: ${studyInstanceUID}`); - return foundMwl.toDicomJson(); + return foundMwl.toGeneralDicomJson(); } } diff --git a/api/dicom-web/controller/PAM-RS/update-patient.js b/api/dicom-web/controller/PAM-RS/update-patient.js index 4bbaa0f2..8f705377 100644 --- a/api/dicom-web/controller/PAM-RS/update-patient.js +++ b/api/dicom-web/controller/PAM-RS/update-patient.js @@ -18,7 +18,7 @@ class UpdatePatientController extends Controller { return this.response .set("content-type", "application/dicom+json") .status(200) - .json(updatedPatient.toDicomJson()); + .json(await updatedPatient.toGeneralDicomJson()); } catch(e) { let apiErrorArrayHandler = new ApiErrorArrayHandler(this.response, this.apiLogger, e); return apiErrorArrayHandler.doErrorResponse(); diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index e7bf051b..16effd4c 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -6,6 +6,7 @@ const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { IncludeFieldsFactory } = require("../service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { raccoonConfig } = require("@root/config-class"); +const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); let Common; if (raccoonConfig.dicomDimseConfig.enableDimse) { @@ -80,12 +81,15 @@ let mwlItemSchema = new mongoose.Schema( } }, methods: { - toDicomJson: function () { + toGeneralDicomJson: async function() { let obj = this.toObject(); delete obj._id; delete obj.id; return obj; }, + toDicomJson: async function () { + return new BaseDicomJson(await this.toGeneralDicomJson()); + }, getAttributes: async function () { let jsonStr = JSON.stringify(this.toDicomJson()); return await Common.getAttributesFromJsonString(jsonStr); From cd64bc7c36c9175be4c05eb486be62e9f40fafe5 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 15:21:18 +0800 Subject: [PATCH 319/365] docs: use ts declare instead of js for dicom types --- .../WADO-RS/bulkdata/service/bulkdata.js | 4 +- .../WADO-RS/service/WADO-RS.service.js | 4 +- .../WADO-RS/service/thumbnail.service.js | 8 ++-- models/DICOM/audit/auditManager.js | 17 +-------- models/DICOM/dicom-json-model.js | 4 +- models/mongodb/models/auditMessage.js | 3 +- models/mongodb/models/mwlitems.model.js | 2 +- models/mongodb/models/patient.model.js | 2 +- models/mongodb/models/series.model.js | 2 +- models/mongodb/models/study.model.js | 2 +- models/mongodb/models/workitems.model.js | 2 +- models/mongodb/schema/dicom.schema.js | 4 +- utils/typeDef/dicom.d.ts | 22 +++++++++++ utils/typeDef/dicom.js | 37 ------------------- 14 files changed, 43 insertions(+), 70 deletions(-) create mode 100644 utils/typeDef/dicom.d.ts delete mode 100644 utils/typeDef/dicom.js diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index 2c8fecc8..cae46bd3 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -57,10 +57,10 @@ class BulkDataService { class BulkDataFactory { /** * - * @param {import("@root/utils/typeDef/dicom").Uids} uids + * @param {Pick} uids */ constructor(uids) { - /** @type {import("@root/utils/typeDef/dicom").Uids} */ + /** @type {Pick} */ this.uids = uids; } diff --git a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 284fa5dc..9130d3de 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -82,7 +82,7 @@ class ImagePathFactory { /** * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + * @param {Pick} uids */ constructor(uids) { /** @type { import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[] } */ @@ -247,7 +247,7 @@ function addHostnameOfBulkDataUrl(metadata, req) { /** * -* @param {import("../../../../../utils/typeDef/dicom").Uids} uids +* @param {Pick} uids * @returns */ function getUidsString(uids) { diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index d18ec8a1..3caddd4c 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -64,7 +64,7 @@ class ThumbnailService { class ThumbnailFactory { /** * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + * @param {Pick} uids */ constructor(uids) { this.uids = uids; @@ -80,7 +80,7 @@ class StudyThumbnailFactory extends ThumbnailFactory { /** * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + * @param {Pick} uids */ async getThumbnailInstance() { let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ @@ -106,7 +106,7 @@ class SeriesThumbnailFactory extends ThumbnailFactory { /** * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + * @param {Pick} uids */ async getThumbnailInstance() { let medianInstance = await InstanceModel.getInstanceOfMedianIndex({ @@ -133,7 +133,7 @@ class InstanceThumbnailFactory extends ThumbnailFactory { /** * - * @param {import("../../../../../utils/typeDef/dicom").Uids} uids + * @param {Pick} uids */ async getThumbnailInstance() { let instanceFramesObj = await renderedService.getInstanceFrameObj({ diff --git a/models/DICOM/audit/auditManager.js b/models/DICOM/audit/auditManager.js index 113de7e7..6c106879 100644 --- a/models/DICOM/audit/auditManager.js +++ b/models/DICOM/audit/auditManager.js @@ -2,16 +2,9 @@ const _ = require("lodash"); const { AuditMessageFactory } = require("./auditMessageFactory"); const { EventType } = require("./eventType"); -const { AuditMessageModel } = require("@models/db/auditMessage.model"); -const { AuditMessageModelLoggerDbImpl } = require("@models/db/auditMessage.loggerImpl"); -const AuditMessageModelMongodbDbImpl = require("@dbModels/auditMessage"); +const { AuditMessageModel } = require("@dbModels/auditMessage"); const { raccoonConfig } = require("@root/config-class"); -/** - * @typedef AuditMessageModel - * @property {(json: JSON) => Promise} createMessage - */ - class AuditManager { constructor(eventType, eventResult, clientAETitle, clientHostname, @@ -156,17 +149,11 @@ class AuditManager { */ async saveToDb_(msg) { try { - await AuditManager.getAuditMessageModel().createMessage(msg); + await AuditMessageModel.createMessage(msg); } catch (e) { throw e; } } - - static getAuditMessageModel() { - if (raccoonConfig.serverConfig.dbType === "sql") - return new AuditMessageModel(new AuditMessageModelLoggerDbImpl()); - return new AuditMessageModel(AuditMessageModelMongodbDbImpl); - } } module.exports.AuditManager = AuditManager; \ No newline at end of file diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 5c137dee..8100cc33 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -112,7 +112,7 @@ class DicomJsonModel { //For temp property that have big value that mongodb cannot save and cause performance issue this.tempBigTagValue = {}; - /** @type {import("../../utils/typeDef/dicom").UIDObject} */ + /** @type {import("@root/utils/typeDef/dicom").DicomUid} */ this.uidObj = {}; } @@ -547,7 +547,7 @@ class DicomJsonBinaryDataModel { class BulkData { constructor(uidObj, filename, pathOfBinaryProperty) { - /** @type {import("../../utils/typeDef/dicom").UIDObject} */ + /** @type {import("@root/utils/typeDef/dicom").DicomUid} */ this.uidObj = uidObj; this.filename = filename; this.pathOfBinaryProperty = pathOfBinaryProperty; diff --git a/models/mongodb/models/auditMessage.js b/models/mongodb/models/auditMessage.js index 0d2681e0..973698fb 100644 --- a/models/mongodb/models/auditMessage.js +++ b/models/mongodb/models/auditMessage.js @@ -23,4 +23,5 @@ let auditMessage = new mongoose.Schema( const auditMessageModel = mongoose.model("auditMessage", auditMessage, "auditMessage"); -module.exports = auditMessageModel; \ No newline at end of file +module.exports = auditMessageModel; +module.exports.AuditMessageModel = auditMessageModel; \ No newline at end of file diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 16effd4c..be7fe8d8 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -32,7 +32,7 @@ let mwlItemSchema = new mongoose.Schema( }, /** * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions * @returns */ getDicomJson: async function (queryOptions) { diff --git a/models/mongodb/models/patient.model.js b/models/mongodb/models/patient.model.js index c7045a86..807a882a 100644 --- a/models/mongodb/models/patient.model.js +++ b/models/mongodb/models/patient.model.js @@ -41,7 +41,7 @@ let patientSchemaOptions = _.merge( }, /** * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions * @returns */ getDicomJsonProjection: function (queryOptions) { diff --git a/models/mongodb/models/series.model.js b/models/mongodb/models/series.model.js index 80d91580..72e32bdd 100644 --- a/models/mongodb/models/series.model.js +++ b/models/mongodb/models/series.model.js @@ -36,7 +36,7 @@ let dicomSeriesSchemaOptions = _.merge( statics: { /** * - * @param {import("@root/utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions * @returns */ getDicomJsonProjection: function (queryOptions) { diff --git a/models/mongodb/models/study.model.js b/models/mongodb/models/study.model.js index 13a28c33..4d1df6e8 100644 --- a/models/mongodb/models/study.model.js +++ b/models/mongodb/models/study.model.js @@ -46,7 +46,7 @@ let dicomStudySchemaOptions = _.merge( statics: { /** * - * @param {import("@root/utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions * @returns */ getDicomJsonProjection: function (queryOptions) { diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index 85d8b94d..fea097b6 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -132,7 +132,7 @@ for (let tag in tagsNeedStore.Patient) { /** * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions * @returns */ workItemSchema.statics.getDicomJson = async function (queryOptions) { diff --git a/models/mongodb/schema/dicom.schema.js b/models/mongodb/schema/dicom.schema.js index 6a83c692..cfdf73cc 100644 --- a/models/mongodb/schema/dicom.schema.js +++ b/models/mongodb/schema/dicom.schema.js @@ -68,7 +68,7 @@ class DicomSchemaOptionsFactory { }, /** * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions * @returns */ getDicomJson: async function (queryOptions) { @@ -99,7 +99,7 @@ class DicomSchemaOptionsFactory { }, /** * - * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions + * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions * @returns */ getDicomJsonProjection: function (queryOptions) { diff --git a/utils/typeDef/dicom.d.ts b/utils/typeDef/dicom.d.ts new file mode 100644 index 00000000..9eb94675 --- /dev/null +++ b/utils/typeDef/dicom.d.ts @@ -0,0 +1,22 @@ +export type DicomUid = { + studyUID?: string; + seriesUID?: string; + instanceUID?: string; + sopClass?: string; + patientID: string; +}; + +export type DicomJsonQueryOptions = { + query: any; + limit?: number; + skip?: number; + retrieveBaseUrl?: string; + includeFields?: string[]; + requestParams?: any; + isRecycle?: boolean; +}; + +export type DicomJsonItem = { + vr: string; + Value: any[]; +}; diff --git a/utils/typeDef/dicom.js b/utils/typeDef/dicom.js deleted file mode 100644 index afe71752..00000000 --- a/utils/typeDef/dicom.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @typedef Uids - * @property {string} studyUID - * @property {string} [seriesUID] - * @property {string} [instanceUID] - */ - -/** - * @typedef {object} UIDObject - * @property {string} studyUID - * @property {string} seriesUID - * @property {string} sopInstanceUID - * @property {string} sopClass - * @property {string} patientID - * - */ - -/** - * @typedef DicomJsonMongoQueryOptions - * @property {object} query - * @property {number} limit - * @property {number} skip - * @property {string} retrieveBaseUrl - * @property {object} requestParams? - * @property {string[]} includeFields - * @property {boolean} [isRecycle] - */ - -/** - * @typedef DicomJsonItem - * @property {string} vr - * @property {any[]} Value - */ - -const DICOM = true; - -module.exports.unUse = {}; From 644c97285db75e7c6b0b775f0b63c60db8da7d5b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 15:24:45 +0800 Subject: [PATCH 320/365] docs: bulkdata types use ts declare instead of js --- .../controller/WADO-RS/bulkdata/service/bulkdata.js | 2 +- utils/typeDef/bulkdata.d.ts | 7 +++++++ utils/typeDef/bulkdata.js | 13 ------------- 3 files changed, 8 insertions(+), 14 deletions(-) create mode 100644 utils/typeDef/bulkdata.d.ts delete mode 100644 utils/typeDef/bulkdata.js diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index cae46bd3..1a65b1e7 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -22,7 +22,7 @@ class BulkDataService { /** * - * @param {import("../../../../../../utils/typeDef/bulkdata").BulkData | + * @param {import("@root/utils/typeDef/bulkdata").BulkData | * import("../../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj } bulkData */ async writeBulkData(bulkData) { diff --git a/utils/typeDef/bulkdata.d.ts b/utils/typeDef/bulkdata.d.ts new file mode 100644 index 00000000..195c74a0 --- /dev/null +++ b/utils/typeDef/bulkdata.d.ts @@ -0,0 +1,7 @@ +export type BulkData = { + studyUID: string; + seriesUID: string; + instanceUID: string; + filename: string; + binaryValuePath: string; +}; \ No newline at end of file diff --git a/utils/typeDef/bulkdata.js b/utils/typeDef/bulkdata.js deleted file mode 100644 index b23b2f6f..00000000 --- a/utils/typeDef/bulkdata.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @typedef {object} BulkData - * @property {string} studyUID - * @property {string} seriesUID - * @property {string} instanceUID - * @property {string} filename - * @property {string} binaryValuePath The json path of the binary value - * - */ - -const BulkData = true; - -module.exports.unUse = {}; From 54877d79a5eae17b40c9b73d93d7a289833dba2c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 15:44:20 +0800 Subject: [PATCH 321/365] docs: WADO-RS types use ts declare instead of js --- .../WADO-RS/bulkdata/service/bulkdata.js | 2 +- .../WADO-RS/service/WADO-RS.service.js | 2 +- .../WADO-RS/service/rendered.service.js | 8 +++---- models/mongodb/models/instance.model.js | 2 +- utils/typeDef/WADO-RS/WADO-RS.def.js | 22 ------------------- utils/typeDef/dicomImage.d.ts | 19 ++++++++++++++++ 6 files changed, 26 insertions(+), 29 deletions(-) delete mode 100644 utils/typeDef/WADO-RS/WADO-RS.def.js create mode 100644 utils/typeDef/dicomImage.d.ts diff --git a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js index 1a65b1e7..e297f5d4 100644 --- a/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ b/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js @@ -23,7 +23,7 @@ class BulkDataService { /** * * @param {import("@root/utils/typeDef/bulkdata").BulkData | - * import("../../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj } bulkData + * import("@root/utils/typeDef/dicomImage").ImagePathObj } bulkData */ async writeBulkData(bulkData) { let absFilename; diff --git a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 9130d3de..74195d88 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -85,7 +85,7 @@ class ImagePathFactory { * @param {Pick} uids */ constructor(uids) { - /** @type { import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[] } */ + /** @type { import("@root/utils/typeDef/dicomImage").ImagePathObj[] } */ this.imagePaths = []; /** @type {Uids} */ this.uids = uids; diff --git a/api/dicom-web/controller/WADO-RS/service/rendered.service.js b/api/dicom-web/controller/WADO-RS/service/rendered.service.js index 9ee02a16..0216f40f 100644 --- a/api/dicom-web/controller/WADO-RS/service/rendered.service.js +++ b/api/dicom-web/controller/WADO-RS/service/rendered.service.js @@ -145,7 +145,7 @@ class RenderedImageMultipartWriter { class FramesWriter { /** * - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[]} imagePaths + * @param {import("@root/utils/typeDef/dicomImage").ImagePathObj[]} imagePaths */ constructor(req, res, imagePaths) { this.request = req; @@ -169,7 +169,7 @@ class FramesWriter { class StudyFramesWriter extends FramesWriter { /** * - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[]} imagePaths + * @param {import("@root/utils/typeDef/dicomImage").ImagePathObj[]} imagePaths */ constructor(req, res, imagePaths) { super(req, res, imagePaths); @@ -269,7 +269,7 @@ This instance NumberOfFrames is : ${this.dicomNumberOfFrames} , But request ${JS /** * * @param {import("http").IncomingMessage} req - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").InstanceFrameObj} instanceFramesObj + * @param {import("@root/utils/typeDef/dicomImage").InstanceFrameObj} instanceFramesObj * @returns */ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { @@ -320,7 +320,7 @@ async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { * * @param {import("express").Request} req * @param {number|number[]} dicomNumberOfFrames - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj} instanceFramesObj + * @param {import("@root/utils/typeDef/dicomImage").ImagePathObj} instanceFramesObj * @param {import("../../../../../utils/multipartWriter").MultipartWriter} multipartWriter */ async function writeRenderedImages(req, dicomNumberOfFrames, instanceFramesObj, multipartWriter) { diff --git a/models/mongodb/models/instance.model.js b/models/mongodb/models/instance.model.js index 49635e2d..d60bdef7 100644 --- a/models/mongodb/models/instance.model.js +++ b/models/mongodb/models/instance.model.js @@ -171,7 +171,7 @@ let dicomSchemaOptions = _.merge( * @param {string} iParam.studyUID * @param {string} iParam.seriesUID * @param {string} iParam.instanceUID - * @returns { Promise | Promise } + * @returns { Promise | Promise } */ getInstanceFrame: async function (iParam) { let { studyUID, seriesUID, instanceUID } = iParam; diff --git a/utils/typeDef/WADO-RS/WADO-RS.def.js b/utils/typeDef/WADO-RS/WADO-RS.def.js deleted file mode 100644 index 36d68090..00000000 --- a/utils/typeDef/WADO-RS/WADO-RS.def.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @typedef { Object } ImagePathObj - * @property { string } studyUID - * @property { string } seriesUID - * @property { string } instanceUID - * @property { string } instancePath - */ - -/** - * @typedef { { - * studyUID: string, - * seriesUID: string, - * instanceUID: string, - * instancePath: string, - * "00280008": import("../dicom").DicomJsonItem, - * "00020010": import("../dicom").DicomJsonItem, - * "00281050": import("../dicom").DicomJsonItem, - * "00281051": import("../dicom").DicomJsonItem - * } } InstanceFrameObj - */ - -module.exports.unUse = {}; \ No newline at end of file diff --git a/utils/typeDef/dicomImage.d.ts b/utils/typeDef/dicomImage.d.ts new file mode 100644 index 00000000..7db267ca --- /dev/null +++ b/utils/typeDef/dicomImage.d.ts @@ -0,0 +1,19 @@ +import type { DicomJsonItem } from "./dicom"; + +export type ImagePathObj = { + studyUID: string; + seriesUID: string; + instanceUID: string; + instancePath: string; +}; + +export type InstanceFrameObj = { + studyUID: string; + seriesUID: string; + instanceUID: string; + instancePath: string; + "00280008": DicomJsonItem; + "00020010": DicomJsonItem; + "00281050": DicomJsonItem; + "00281051": DicomJsonItem; +}; From d32e272c4880d954e76cdb209954e22a39838bee Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 15:55:34 +0800 Subject: [PATCH 322/365] docs: STOW-RS types use ts declare instead of js --- .../service/request-multipart-parser.js | 2 +- utils/typeDef/STOW-RS/STOW-RS.d.ts | 32 +++++++++++++++++++ utils/typeDef/dicom.d.ts | 4 +++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 utils/typeDef/STOW-RS/STOW-RS.d.ts diff --git a/api/dicom-web/controller/STOW-RS/service/request-multipart-parser.js b/api/dicom-web/controller/STOW-RS/service/request-multipart-parser.js index c10c8af3..b8b94d04 100644 --- a/api/dicom-web/controller/STOW-RS/service/request-multipart-parser.js +++ b/api/dicom-web/controller/STOW-RS/service/request-multipart-parser.js @@ -12,7 +12,7 @@ class StowRsRequestMultipartParser { /** * - * @return {Promise} + * @return {Promise} */ async parse() { return new Promise((resolve, reject) => { diff --git a/utils/typeDef/STOW-RS/STOW-RS.d.ts b/utils/typeDef/STOW-RS/STOW-RS.d.ts new file mode 100644 index 00000000..569888e6 --- /dev/null +++ b/utils/typeDef/STOW-RS/STOW-RS.d.ts @@ -0,0 +1,32 @@ +import type { Fields, File } from "formidable"; +import type { GeneralDicomJson } from "../dicom"; + +type Multipart = { + fields?: Fields; + files?: File[]; +}; + +export type MultipartParseResult = { + status: boolean; + error?: string; + multipart: Multipart; +}; + +export type SaveDicomFileResult = { + /** The path of saved file's directory */ + fullPath: string; + + /** The relative path of saved DICOM instance file. e.g. /files/123.dcm */ + relativePath: string; + + /** The full path of saved DICOM instance file. e.g. /home/app/files/123.dcm */ + instancePath: string; + + /** The relative path of series level directory */ + seriesPath: string; + + /** The relative path of study level directory */ + studyPath: string; + + dicomJson: GeneralDicomJson; +}; diff --git a/utils/typeDef/dicom.d.ts b/utils/typeDef/dicom.d.ts index 9eb94675..70bf1f20 100644 --- a/utils/typeDef/dicom.d.ts +++ b/utils/typeDef/dicom.d.ts @@ -20,3 +20,7 @@ export type DicomJsonItem = { vr: string; Value: any[]; }; + +export type GeneralDicomJson = { + [key: string]: DicomJsonItem; +} From 64fa4e21300b589c696b050882e0afc67c462faf Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 16:16:24 +0800 Subject: [PATCH 323/365] refactor: drop SQL condition --- config-class.js | 23 +--------------- models/DICOM/audit/auditMessageFactory.js | 19 +++++--------- server.js | 32 +++++------------------ 3 files changed, 14 insertions(+), 60 deletions(-) diff --git a/config-class.js b/config-class.js index c1de7812..40580adb 100644 --- a/config-class.js +++ b/config-class.js @@ -24,19 +24,6 @@ function generateUidFromGuid(iGuid) { return `2.25.${bigInteger.toString()}`; //Output the previus parsed integer as string by adding `2.25.` as prefix } -class SqlDbConfig { - constructor() { - this.host = env.get("SQL_HOST").default("127.0.0.1").asString(); - this.port = env.get("SQL_PORT").default("5432").asString(); - this.database = env.get("SQL_DB").default("raccoon").asString(); - this.dialect = env.get("SQL_TYPE").default("postgres").asString(); - this.username = env.get("SQL_USERNAME").default("postgres").asString(); - this.password = env.get("SQL_PASSWORD").default("postgres").asString(); - this.logging = env.get("SQL_LOGGING").default("false").asBool(); - this.forceSync = env.get("SQL_FORCE_SYNC").default("false").asBool(); - this.dbName = this.database; - } -} class MongoDbConfig { constructor() { @@ -56,7 +43,6 @@ class ServerConfig { this.host = env.get("SERVER_HOST").default("127.0.0.1").asString(); this.port = env.get("SERVER_PORT").default("8081").asInt(); this.secretKey = env.get("SERVER_SESSION_SECRET_KEY").asString(); - this.dbType = env.get("SERVER_DB_TYPE").default("mongodb").asEnum(["mongodb", "sql"]); } } @@ -82,11 +68,7 @@ class RaccoonConfig { constructor() { this.serverConfig = new ServerConfig(); - if (this.serverConfig.dbType === "mongodb") { - this.dbConfig = new MongoDbConfig(); - } else if (this.serverConfig.dbType === "sql") { - this.dbConfig = new SqlDbConfig(); - } + this.dbConfig = new MongoDbConfig(); this.dicomWebConfig = new DicomWebConfig(); this.dicomDimseConfig = new DimseConfig(); @@ -100,9 +82,6 @@ class RaccoonConfig { /** @type {string} */ this.mediaStorageID = this.dbConfig.dbName; - this.aeTitle = this.dicomWebConfig.aeTitle; - // this.aeTitle = this.dicomDimseConfig.enableDimse ? this.dicomDimseConfig.getAeTitle() : this.dicomWebConfig.aeTitle; - this.aeTitle = this.dicomDimseConfig.enableDimse ? this.dicomDimseConfig.aeTitle : this.dicomWebConfig.aeTitle; if (!this.aeTitle) throw new Error("Missing required config `aeTitle`"); diff --git a/models/DICOM/audit/auditMessageFactory.js b/models/DICOM/audit/auditMessageFactory.js index 87612832..10d91e38 100644 --- a/models/DICOM/audit/auditMessageFactory.js +++ b/models/DICOM/audit/auditMessageFactory.js @@ -23,8 +23,10 @@ const { EventIdentificationBuilder } = require("@dcm4che/audit/EventIdentificati const { EventType } = require("./eventType"); const { default: ActiveParticipantBuilder } = require("@dcm4che/audit/ActiveParticipantBuilder"); const { AuditMessages$UserIDTypeCode } = require("@dcm4che/audit/AuditMessages$UserIDTypeCode"); + const { ParticipatingObjectFactory } = require("./participatingObjectFactory"); -const { raccoonConfig } = require("@root/config-class"); +const { InstanceModel } = require("@dbModels/instance.model"); + class AuditMessageFactory { constructor() { } @@ -71,7 +73,7 @@ class AuditMessageFactory { let patientParticipatingObject; for (let i = 0; i < StudyInstanceUIDs.length; i++) { let participatingObjectFactory = new ParticipatingObjectFactory( - this.getInstanceModel(), + InstanceModel, StudyInstanceUIDs[i] ); @@ -129,7 +131,7 @@ class AuditMessageFactory { let patientParticipatingObject; for (let i = 0; i < StudyInstanceUIDs.length; i++) { let participatingObjectFactory = new ParticipatingObjectFactory( - this.getInstanceModel(), + InstanceModel, StudyInstanceUIDs[i] ); @@ -196,7 +198,7 @@ class AuditMessageFactory { let patientParticipatingObject; for (let i = 0; i < studyInstanceUIDs.length; i++) { let participatingObjectFactory = new ParticipatingObjectFactory( - this.getInstanceModel(), + InstanceModel, studyInstanceUIDs[i] ); @@ -349,15 +351,6 @@ class AuditMessageFactory { return activateParticipants; } - getInstanceModel() { - if (raccoonConfig.serverConfig.dbType === "sql") { - const sequelizeInstance = require("@models/sql/instance"); - return sequelizeInstance.model("Instance"); - } else { - const mongoose = require("mongoose"); - return mongoose.model("dicom"); - } - } } diff --git a/server.js b/server.js index cc601547..e5368cdd 100644 --- a/server.js +++ b/server.js @@ -1,11 +1,7 @@ RegExp.prototype.toJSON = RegExp.prototype.toString; const { raccoonConfig } = require("./config-class"); -if (raccoonConfig.serverConfig.dbType === "mongodb") { - require('module-alias')(__dirname + "/config/modula-alias/mongodb"); -} else if (raccoonConfig.serverConfig.dbType === "sql") { - require('module-alias')(__dirname + "/config/modula-alias/sql"); -} +require('module-alias')(__dirname + "/config/modula-alias/mongodb"); const { app, server } = require("./app"); const bodyParser = require("body-parser"); @@ -15,26 +11,12 @@ const compress = require("compression"); const cors = require("cors"); const os = require("os"); -let sessionStore; -let dbInstance; -let sessionStoreOption; -if (raccoonConfig.serverConfig.dbType === "mongodb") { - sessionStore = require("connect-mongo"); - dbInstance = require("mongoose"); - - sessionStoreOption = sessionStore.create({ - client: dbInstance.connection.getClient(), - dbName: raccoonConfig.dbConfig.dbName - }); - -} else if (raccoonConfig.serverConfig.dbType === "sql") { - sessionStore = require("connect-session-sequelize")(session.Store); - dbInstance = require("./models/sql/instance"); - - sessionStoreOption = new sessionStore({ - db: dbInstance - }); -} +let sessionStore = require("connect-mongo");; +let dbInstance = require("mongoose"); +let sessionStoreOption = sessionStore.create({ + client: dbInstance.connection.getClient(), + dbName: raccoonConfig.dbConfig.dbName +}); const passport = require("passport"); const { DcmQrScp } = require('@dimse'); From 371e600758aed3cc659323ad953ec2c89ad7cb53 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 16:18:39 +0800 Subject: [PATCH 324/365] refactor: exports `AuditMessageModel` of `AuditMessageModelLoggerDbImpl` --- models/db/auditMessage.loggerImpl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/db/auditMessage.loggerImpl.js b/models/db/auditMessage.loggerImpl.js index 3c41e79f..22d94eb1 100644 --- a/models/db/auditMessage.loggerImpl.js +++ b/models/db/auditMessage.loggerImpl.js @@ -1,9 +1,9 @@ const { logger } = require("@root/utils/logs/log"); class AuditMessageModelLoggerDbImpl { - createMessage(msg) { + static async createMessage(msg) { logger.info(JSON.stringify(msg)); } } -module.exports.AuditMessageModelLoggerDbImpl = AuditMessageModelLoggerDbImpl; \ No newline at end of file +module.exports.AuditMessageModel = AuditMessageModelLoggerDbImpl; \ No newline at end of file From d66a26da815e9bd1b746792d736c3b45b77ffb50 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 16:18:57 +0800 Subject: [PATCH 325/365] chore: remove unused file --- models/db/auditMessage.model.js | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 models/db/auditMessage.model.js diff --git a/models/db/auditMessage.model.js b/models/db/auditMessage.model.js deleted file mode 100644 index 53b64e68..00000000 --- a/models/db/auditMessage.model.js +++ /dev/null @@ -1,11 +0,0 @@ -class AuditMessageModel { - constructor(dbModel) { - this.dbModel = dbModel; - } - async createMessage(msg) { - return await this.dbModel.createMessage(msg); - } -} - - -module.exports.AuditMessageModel = AuditMessageModel; \ No newline at end of file From fbb2a4c0d4ce1c0d265b5858a6b807ea57e564e5 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 16:21:05 +0800 Subject: [PATCH 326/365] refactor: drop all files related to SQL --- api-sql/WADO-URI/service/WADO-URI.service.js | 99 ---- .../service/change-filtered-mwlItem-status.js | 52 -- .../MWL-RS/service/change-mwlItem-status.js | 44 -- .../MWL-RS/service/count-mwlItem.service.js | 27 - .../MWL-RS/service/create-mwlItem.service.js | 40 -- .../MWL-RS/service/get-mwlItem.service.js | 36 -- .../MWL-RS/service/query/mwlQueryBuilder.js | 227 -------- .../PAM-RS/service/create-patient.service.js | 29 - .../PAM-RS/service/delete-patient.service.js | 29 - .../PAM-RS/service/update-patient.service.js | 17 - .../QIDO-RS/service/QIDO-RS.service.js | 17 - .../QIDO-RS/service/dicomCodeQueryBuilder.js | 90 --- .../QIDO-RS/service/instanceQueryBuilder.js | 435 --------------- .../QIDO-RS/service/patientQueryBuilder.js | 50 -- .../service/query-dicom-json-factory.js | 17 - .../QIDO-RS/service/querybuilder.js | 495 ----------------- .../QIDO-RS/service/seriesQueryBuilder.js | 209 ------- .../UPS-RS/service/base-workItem.service.js | 76 --- .../UPS-RS/service/cancel.service.js | 26 - .../service/change-workItem-state.service.js | 56 -- .../UPS-RS/service/create-workItem.service.js | 39 -- .../UPS-RS/service/get-workItem.service.js | 39 -- .../UPS-RS/service/query/upsQueryBuilder.js | 229 -------- .../UPS-RS/service/subscribe.service.js | 147 ----- .../service/suspend-subscription.service.js | 53 -- .../UPS-RS/service/unsubscribe.service.js | 105 ---- .../UPS-RS/service/update-workItem.service.js | 73 --- .../WADO-RS/bulkdata/service/bulkdata.js | 88 --- .../WADO-RS/deletion/service/delete.js | 74 --- .../WADO-RS/service/WADO-RS.service.js | 46 -- .../WADO-RS/service/rendered.service.js | 316 ----------- .../WADO-RS/service/thumbnail.service.js | 33 -- dimse-sql/index.js | 53 -- dimse-sql/instanceQueryTask.js | 90 --- dimse-sql/patientQueryTask.js | 83 --- dimse-sql/queryBuilder.js | 27 - dimse-sql/seriesQueryTask.js | 93 ---- dimse-sql/studyQueryTask.js | 105 ---- dimse-sql/utils.js | 112 ---- models/sql/deleteSchedule.js | 152 ------ models/sql/dicom-json-model.js | 94 ---- models/sql/generate-erd.js | 14 - models/sql/init.js | 213 -------- models/sql/initializer.js | 1 - models/sql/instance.js | 9 - models/sql/models/baseDicom.model.js | 27 - models/sql/models/dicomBulkData.model.js | 30 - models/sql/models/dicomCode.model.js | 38 -- models/sql/models/dicomContentSQ.model.js | 42 -- models/sql/models/dicomToJpegTask.model.js | 61 --- models/sql/models/instance.model.js | 240 -------- models/sql/models/mwlitems.model.js | 195 ------- models/sql/models/patient.model.js | 83 --- models/sql/models/personName.model.js | 94 ---- models/sql/models/series.model.js | 167 ------ .../models/seriesRequestAttributes.model.js | 41 -- models/sql/models/study.model.js | 210 ------- .../sql/models/upsGlobalSubscription.model.js | 56 -- .../sql/models/upsRequestAttributes.model.js | 41 -- models/sql/models/upsSubscription.model.js | 27 - .../sql/models/verifyingObserverSQ.model.js | 34 -- models/sql/models/workitems.model.js | 204 ------- models/sql/po/instance.po.js | 512 ------------------ models/sql/po/mwlItem.po.js | 164 ------ models/sql/po/patient.po.js | 80 --- models/sql/po/series.po.js | 186 ------- models/sql/po/study.po.js | 84 --- models/sql/po/upsWorkItem.po.js | 222 -------- models/sql/po/utils.js | 7 - models/sql/vrTypeMapping.js | 40 -- 70 files changed, 7244 deletions(-) delete mode 100644 api-sql/WADO-URI/service/WADO-URI.service.js delete mode 100644 api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js delete mode 100644 api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js delete mode 100644 api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js delete mode 100644 api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js delete mode 100644 api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js delete mode 100644 api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js delete mode 100644 api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js delete mode 100644 api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js delete mode 100644 api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js delete mode 100644 api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js delete mode 100644 api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js delete mode 100644 api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js delete mode 100644 dimse-sql/index.js delete mode 100644 dimse-sql/instanceQueryTask.js delete mode 100644 dimse-sql/patientQueryTask.js delete mode 100644 dimse-sql/queryBuilder.js delete mode 100644 dimse-sql/seriesQueryTask.js delete mode 100644 dimse-sql/studyQueryTask.js delete mode 100644 dimse-sql/utils.js delete mode 100644 models/sql/deleteSchedule.js delete mode 100644 models/sql/dicom-json-model.js delete mode 100644 models/sql/generate-erd.js delete mode 100644 models/sql/init.js delete mode 100644 models/sql/initializer.js delete mode 100644 models/sql/instance.js delete mode 100644 models/sql/models/baseDicom.model.js delete mode 100644 models/sql/models/dicomBulkData.model.js delete mode 100644 models/sql/models/dicomCode.model.js delete mode 100644 models/sql/models/dicomContentSQ.model.js delete mode 100644 models/sql/models/dicomToJpegTask.model.js delete mode 100644 models/sql/models/instance.model.js delete mode 100644 models/sql/models/mwlitems.model.js delete mode 100644 models/sql/models/patient.model.js delete mode 100644 models/sql/models/personName.model.js delete mode 100644 models/sql/models/series.model.js delete mode 100644 models/sql/models/seriesRequestAttributes.model.js delete mode 100644 models/sql/models/study.model.js delete mode 100644 models/sql/models/upsGlobalSubscription.model.js delete mode 100644 models/sql/models/upsRequestAttributes.model.js delete mode 100644 models/sql/models/upsSubscription.model.js delete mode 100644 models/sql/models/verifyingObserverSQ.model.js delete mode 100644 models/sql/models/workitems.model.js delete mode 100644 models/sql/po/instance.po.js delete mode 100644 models/sql/po/mwlItem.po.js delete mode 100644 models/sql/po/patient.po.js delete mode 100644 models/sql/po/series.po.js delete mode 100644 models/sql/po/study.po.js delete mode 100644 models/sql/po/upsWorkItem.po.js delete mode 100644 models/sql/po/utils.js delete mode 100644 models/sql/vrTypeMapping.js diff --git a/api-sql/WADO-URI/service/WADO-URI.service.js b/api-sql/WADO-URI/service/WADO-URI.service.js deleted file mode 100644 index 681070aa..00000000 --- a/api-sql/WADO-URI/service/WADO-URI.service.js +++ /dev/null @@ -1,99 +0,0 @@ -const fs = require("fs"); -const _ = require("lodash"); -const renderedService = require("../../dicom-web/controller/WADO-RS/service/rendered.service"); -const { Dcm2JpgExecutor } = require("@models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); -const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("@models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); -const sharp = require('sharp'); -const Magick = require("@models/magick"); -const { NotFoundInstanceError, InvalidFrameNumberError, InstanceGoneError } = require("@error/dicom-instance"); -const { WadoUriService } = require("@root/api/WADO-URI/service/WADO-URI.service"); -const { InstanceModel } = require("@models/sql/models/instance.model"); -const { ApiLogger } = require("@root/utils/logs/api-logger"); -class SqlWadoUriService extends WadoUriService{ - - /** - * - * @param {import("http").IncomingMessage} req - * @param {import("http").ServerResponse} res - */ - constructor(req, res, apiLogger) { - super(req, res); - this.apiLogger = apiLogger; - } - - async getDicomInstancePathObj() { - let { - studyUID, - seriesUID, - objectUID: instanceUID - } = this.request.query; - - let imagePathObj = await InstanceModel.getPathOfInstance({ - studyUID, - seriesUID, - instanceUID - }); - - if (imagePathObj) { - - try { - await fs.promises.access(imagePathObj.instancePath, fs.constants.F_OK); - } catch(e) { - console.error(e); - throw new InstanceGoneError("The image is deleted permanently, but meta data remain"); - } - - return imagePathObj; - } - - throw new NotFoundInstanceError("Not found instance"); - } - - async handleFrameNumberAndGetImageObj() { - let imagePathObj = await this.getDicomInstancePathObj(); - let instanceFramesObj = await renderedService.getInstanceFrameObj(imagePathObj); - let instanceTotalFrameNumber = _.get(instanceFramesObj, "x00280008") ? _.get(instanceFramesObj, "x00280008") : 1; - - let windowCenter = _.get(instanceFramesObj, "x00281050.0", ""); - let windowWidth = _.get(instanceFramesObj, "x00281051.0", ""); - - let transferSyntax = _.get(instanceFramesObj, "x00020010"); - let frameNumber = parseInt(_.get(this.request.query, "frameNumber", 1)); - - if (frameNumber > instanceTotalFrameNumber) { - throw new InvalidFrameNumberError(`Invalid Frame Number, total ${instanceTotalFrameNumber}, but requested ${frameNumber}`); - } - - /** @type {Dcm2JpgExecutor$Dcm2JpgOptions} */ - let options = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync(); - options.frameNumber = frameNumber; - - if (windowCenter && windowWidth) { - options.windowCenter = windowCenter; - options.windowWidth = windowWidth; - } - - let dicomFilename = instanceFramesObj.instancePath; - let jpegFile = dicomFilename.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`); - - let getFrameImageStatus = await Dcm2JpgExecutor.convertDcmToJpgFromFilename( - dicomFilename, - jpegFile, - options - ); - - if (getFrameImageStatus.status) { - - return { - imageSharp: sharp(jpegFile), - magick: new Magick(jpegFile) - }; - } - - throw new NotFoundInstanceError("Not found DICOM Instance's Jpeg, may convert error"); - } - -} - -module.exports.WadoUriService = SqlWadoUriService; -module.exports.NotFoundInstanceError = NotFoundInstanceError; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js b/api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js deleted file mode 100644 index b07fb75b..00000000 --- a/api-sql/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js +++ /dev/null @@ -1,52 +0,0 @@ -const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); -const { ChangeFilteredMwlItemStatusService } = require("@root/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status"); -const { cloneDeep, set } = require("lodash"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); -const { MwlQueryBuilder } = require("./query/mwlQueryBuilder"); -const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); - -class SqlChangeFilteredMwlItemStatusService extends ChangeFilteredMwlItemStatusService { - constructor(req, res) { - super(req, res); - } - - async changeMwlItemsStatus() { - let { status } = this.request.params; - let mwlItems = await this.getMwlItems(); - - if (mwlItems.length === 0) { - throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "Can not found any MWL item from query", 404); - } - - for (let mwlItem of mwlItems) { - mwlItem.status = status; - set(mwlItem.json, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); - mwlItem.changed("json", true); - await mwlItem.save(); - } - - return mwlItems.length; - } - - async getMwlItems() { - let queryBuilder = new MwlQueryBuilder({ - query: this.query - }); - let q = queryBuilder.build(); - return await MwlItemModel.findAll(q); - } - - initQuery_() { - let query = cloneDeep(this.request.query); - let queryKeys = Object.keys(query).sort(); - for (let i = 0; i < queryKeys.length; i++) { - let queryKey = queryKeys[i]; - if (!query[queryKey]) delete query[queryKey]; - } - - this.query = convertAllQueryToDicomTag(query, false); - } -} - -module.exports.ChangeFilteredMwlItemStatusService = SqlChangeFilteredMwlItemStatusService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js b/api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js deleted file mode 100644 index 746738ae..00000000 --- a/api-sql/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js +++ /dev/null @@ -1,44 +0,0 @@ -const _ = require("lodash"); -const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { ChangeMwlItemStatusService } = require("@root/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status"); -const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); -const { Op } = require("sequelize"); - -class SqlChangeMwlItemStatusService extends ChangeMwlItemStatusService { - constructor(req, res) { - super(req, res); - } - - async changeMwlItemsStatus() { - let { status } = this.request.params; - let mwlItem = await this.getMwlItemByStudyUIDAndSpsID(); - if (!mwlItem) { - throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "No such object instance", 404); - } - - mwlItem.sps_status = status; - _.set(mwlItem.json, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); - mwlItem.changed("json", true); - await mwlItem.save(); - - return mwlItem.json; - } - - async getMwlItemByStudyUIDAndSpsID() { - return await MwlItemModel.findOne({ - where: { - [Op.and]: [ - { - sps_id: this.request.params.spsID - }, - { - study_instance_uid: this.request.params.studyUID - } - ] - } - }); - } -} - -module.exports.ChangeMwlItemStatusService = SqlChangeMwlItemStatusService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js deleted file mode 100644 index 144cbd57..00000000 --- a/api-sql/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js +++ /dev/null @@ -1,27 +0,0 @@ -const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); -const { GetMwlItemCountService } = require("@root/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); -const { cloneDeep } = require("lodash"); - -class SqlGetMwlItemCountService extends GetMwlItemCountService{ - constructor(req, res) { - super(req, res); - } - - async getMwlItemCount() { - return await MwlItemModel.getCount(this.query); - } - - initQuery_() { - let query = cloneDeep(this.request.query); - let queryKeys = Object.keys(query).sort(); - for (let i = 0; i < queryKeys.length; i++) { - let queryKey = queryKeys[i]; - if (!query[queryKey]) delete query[queryKey]; - } - - this.query = convertAllQueryToDicomTag(query, false); - } -} - -module.exports.GetMwlItemCountService = SqlGetMwlItemCountService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js deleted file mode 100644 index ab445f8d..00000000 --- a/api-sql/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js +++ /dev/null @@ -1,40 +0,0 @@ -const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { PatientModel } = require("@models/sql/models/patient.model"); -const { CreateMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { MwlItemPersistentObject } = require("@models/sql/po/mwlItem.po"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); - -class SqlCreateMwlItemService extends CreateMwlItemService { - constructor(req, res) { - super(req, res); - this.requestMwlItemDicomJsonModel = new DicomJsonModel(this.requestMwlItem[0]); - } - - async checkPatientExist() { - let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); - let patientCount = await PatientModel.count({ - where: { - x00100020: patientID - } - }); - if (patientCount <= 0) { - throw new DicomWebServiceError( - DicomWebStatusCodes.MissingAttribute, - `Patient[id=${patientID}] does not exists`, - 404 - ); - } - } - - async createOrUpdateMwl(mwlDicomJson) { - let studyInstanceUID = mwlDicomJson.getValue(dictionary.keyword.StudyInstanceUID); - let patientID = this.requestMwlItemDicomJsonModel.getString("00100020"); - let mwlItemPO = new MwlItemPersistentObject(mwlDicomJson.dicomJson, await PatientModel.findOne({ where: { x00100020: patientID } })); - let mwlItem = await mwlItemPO.save(); - this.apiLogger.logger.info(`create mwl item: ${studyInstanceUID}`); - return mwlItem.json; - } -} - -module.exports.CreateMwlItemService = SqlCreateMwlItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js deleted file mode 100644 index f6bf5fac..00000000 --- a/api-sql/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js +++ /dev/null @@ -1,36 +0,0 @@ -const { MwlItemModel } = require("@models/sql/models/mwlitems.model"); -const { GetMwlItemService } = require("@root/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); -const { cloneDeep } = require("lodash"); - -class SqlGetMwlItemService extends GetMwlItemService { - constructor(req, res) { - super(req, res); - } - - async getMwlItems() { - let queryOptions = { - query: this.query, - skip: this.skip_, - limit: this.limit_, - requestParams: this.request.params - }; - - let docs = await MwlItemModel.getDicomJson(queryOptions); - - return docs; - } - - initQuery_() { - let query = cloneDeep(this.request.query); - let queryKeys = Object.keys(query).sort(); - for (let i = 0; i < queryKeys.length; i++) { - let queryKey = queryKeys[i]; - if (!query[queryKey]) delete query[queryKey]; - } - - this.query = convertAllQueryToDicomTag(query, false); - } -} - -module.exports.GetMwlItemService = SqlGetMwlItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js b/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js deleted file mode 100644 index 31d04db5..00000000 --- a/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder.js +++ /dev/null @@ -1,227 +0,0 @@ -const sequelize = require("@models/sql/instance"); -const { PatientQueryBuilder } = require("../../../QIDO-RS/service/patientQueryBuilder"); -const { BaseQueryBuilder } = require("../../../QIDO-RS/service/querybuilder"); -const { DicomCodeQueryBuilder } = require("../../../QIDO-RS/service/dicomCodeQueryBuilder"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); - -class MwlQueryBuilder extends BaseQueryBuilder { - constructor(queryOptions) { - super(queryOptions); - - let patientQueryBuilder = new PatientQueryBuilder(queryOptions); - let patientQuery = patientQueryBuilder.build(); - this.includeQueries.push({ - model: sequelize.model("Patient"), - attributes: ["x00100020"], - ...patientQuery, - required: true - }); - - this.createCodeQueries(dictionary.keyword.InstitutionalDepartmentTypeCodeSequence); - this.createCodeQueries(dictionary.keyword.InstitutionCodeSequence); - this.createCodeQueries(dictionary.keyword.ScheduledProtocolCodeSequence); - this.createIssuerOfAccessionNumberSequenceQueries(); - this.createIssuerOfAdmissionIdSequenceQueries(); - this.createSpsQueries(); - } - - getStudyInstanceUID(values) { - return this.getOrQuery( - dictionary.keyword.StudyInstanceUID, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - getAccessionNumber(values) { - return this.getOrQuery( - dictionary.keyword.AccessionNumber, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - getRequestedProcedureID(values) { - return this.getOrQuery( - dictionary.keyword.RequestedProcedureID, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - getAdmissionID(values) { - return this.getOrQuery( - dictionary.keyword.AdmissionID, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - getInstitutionName(values) { - return this.getOrQuery( - dictionary.keyword.InstitutionName, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - getInstitutionDepartmentName(values) { - return this.getOrQuery( - dictionary.keyword.InstitutionalDepartmentName, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - createCodeQueries(tag) { - let dicomCodeQueryBuilder = new DicomCodeQueryBuilder(this, dictionary.tag[tag]); - this[`${tag}.00080100`] = (values) => dicomCodeQueryBuilder.getCodeValue(values); - this[`${tag}.00080102`] = (values) => dicomCodeQueryBuilder.getCodingSchemeDesignator(values); - this[`${tag}.00080103`] = (values) => dicomCodeQueryBuilder.getCodingSchemeVersion(values); - this[`${tag}.00080104`] = (values) => dicomCodeQueryBuilder.getCodeMeaning(values); - } - - createIssuerOfAccessionNumberSequenceQueries() { - this[`${dictionary.keyword.IssuerOfAccessionNumberSequence}.${dictionary.keyword.LocalNamespaceEntityID}`] = (values) => { - return this.getOrQuery( - `accno_local_id`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.IssuerOfAccessionNumberSequence}.${dictionary.keyword.UniversalEntityID}`] = (values) => { - return this.getOrQuery( - `accno_universal_id`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.IssuerOfAccessionNumberSequence}.${dictionary.keyword.UniversalEntityIDType}`] = (values) => { - return this.getOrQuery( - `accno_universal_id_type`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - } - - createIssuerOfAdmissionIdSequenceQueries() { - this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.LocalNamespaceEntityID}`] = (values) => { - return this.getOrQuery( - `issuer_admission_local_id`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityID}`] = (values) => { - return this.getOrQuery( - `issuer_admission_universal_id`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityIDType}`] = (values) => { - return this.getOrQuery( - `issuer_admission_universal_id_type`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - } - - createSpsQueries() { - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.StationAETitle}`] = (values) => { - return this.getOrQuery( - `station_ae_title`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.StationName}`] = (values) => { - return this.getOrQuery( - `station_name`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepStartDate}`] = (values) => { - return this.getOrQuery( - `start_date`, - values, - BaseQueryBuilder.prototype.getDateQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepEndDate}`] = (values) => { - return this.getOrQuery( - `end_date`, - values, - BaseQueryBuilder.prototype.getDateQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepStartTime}`] = (values) => { - return this.getOrQuery( - `start_time`, - values, - BaseQueryBuilder.prototype.getTimeQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepEndTime}`] = (values) => { - return this.getOrQuery( - `end_time`, - values, - BaseQueryBuilder.prototype.getTimeQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledPerformingPhysicianName}`] = (values) => { - let q = this.getOrQuery( - `physician_name`, - values, - BaseQueryBuilder.prototype.getPersonNameQuery.bind(this) - ); - this.includeQueries.push({ - model: sequelize.model("PersonName"), - as: dictionary.tag["00400006"], - where: { - ...q - }, - attributes: [] - }); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepDescription}`] = (values) => { - return this.getOrQuery( - `description`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepID}`] = (values) => { - return this.getOrQuery( - `sps_id`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.ScheduledProcedureStepSequence}.${dictionary.keyword.ScheduledProcedureStepStatus}`] = (values) => { - return this.getOrQuery( - `sps_status`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - } -} - -module.exports.MwlQueryBuilder = MwlQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js b/api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js deleted file mode 100644 index 8c142725..00000000 --- a/api-sql/dicom-web/controller/PAM-RS/service/create-patient.service.js +++ /dev/null @@ -1,29 +0,0 @@ -const { PatientPersistentObject } = require("@models/sql/po/patient.po"); -const { CreatePatientService } = require("@root/api/dicom-web/controller/PAM-RS/service/create-patient.service"); -const { set } = require("lodash"); -const shortHash = require("shorthash2"); -const { v4: uuidV4 } = require("uuid"); - -class SqlCreatePatientService extends CreatePatientService { - constructor(req, res) { - super(req, res); - } - - async create() { - - let incomingPatient = this.request.body; - let patientID = shortHash(uuidV4()); - set(incomingPatient, "patientID", patientID); - set(incomingPatient, "00100020.Value", [ - patientID - ]); - - const patient = new PatientPersistentObject(incomingPatient); - await patient.createPatient(); - return { - patientID - }; - } -} - -module.exports.CreatePatientService = SqlCreatePatientService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js b/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js deleted file mode 100644 index 6efc8ad4..00000000 --- a/api-sql/dicom-web/controller/PAM-RS/service/delete-patient.service.js +++ /dev/null @@ -1,29 +0,0 @@ -const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { PatientModel } = require("@models/sql/models/patient.model"); -const { DeletePatientService } = require("@root/api/dicom-web/controller/PAM-RS/service/delete-patient.service"); - -class SqlDeletePatientService extends DeletePatientService { - constructor(req, res) { - super(req, res); - } - - async delete() { - let { patientID } = this.request.params; - let patient = await PatientModel.findOne({ - where: { - "x00100020": patientID - } - }); - if (!patient) { - throw new DicomWebServiceError( - DicomWebStatusCodes.NoSuchObjectInstance, - `Patient "${this.request.params.patientID}" does not exist.`, - 404 - ); - } - - await patient.incrementDeleteStatus(); - } -} - -module.exports.DeletePatientService = SqlDeletePatientService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js b/api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js deleted file mode 100644 index e080929c..00000000 --- a/api-sql/dicom-web/controller/PAM-RS/service/update-patient.service.js +++ /dev/null @@ -1,17 +0,0 @@ -const { PatientPersistentObject } = require("@models/sql/po/patient.po"); -const { UpdatePatientService } = require("@root/api/dicom-web/controller/PAM-RS/service/update-patient.service"); - - -class SqlUpdatePatientService extends UpdatePatientService { - constructor(req, res) { - super(req, res); - } - - async update() { - let patientPO = new PatientPersistentObject(this.incomingPatient); - - return await patientPO.createPatient(); - } -} - -module.exports.UpdatePatientService = SqlUpdatePatientService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js deleted file mode 100644 index fdd27330..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js +++ /dev/null @@ -1,17 +0,0 @@ -const _ = require("lodash"); - -const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); - -QidoRsService.prototype.initQuery_ = function () { - let query = _.cloneDeep(this.request.query); - let queryKeys = Object.keys(query).sort(); - for (let i = 0; i < queryKeys.length; i++) { - let queryKey = queryKeys[i]; - if (!query[queryKey]) delete query[queryKey]; - } - - this.query = convertAllQueryToDicomTag(query, false); -}; - -module.exports.QidoRsService = QidoRsService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js deleted file mode 100644 index 6f03d413..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/service/dicomCodeQueryBuilder.js +++ /dev/null @@ -1,90 +0,0 @@ -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { BaseQueryBuilder } = require("./querybuilder"); -const { DicomCodeModel } = require("@models/sql/models/dicomCode.model"); -const { Op } = require("sequelize"); - -class DicomCodeQueryBuilder { - constructor(queryBuilder, codeTableName) { - /** @type { import("../../UPS-RS/service/query/upsQueryBuilder") } */ - this.queryBuilder = queryBuilder; - this.codeTableName = codeTableName; - } - - isModelIncluded() { - this.queryBuilder.includeQueries.forEach(v=> console.log(v.model.getTableName())); - return this.queryBuilder.includeQueries.find(v => v.model.getTableName() === this.codeTableName || v.as === this.codeTableName); - } - - /** - * - * @param {string[]} values - */ - getCodeValue(values) { - let q = this.queryBuilder.getOrQuery( - dictionary.keyword.CodeValue, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.queryBuilder) - ); - this.addQuery(q); - } - - /** - * - * @param {string[]} values - */ - getCodingSchemeDesignator(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodingSchemeDesignator, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - /** - * - * @param {string[]} values - */ - getCodingSchemeVersion(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodingSchemeVersion, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - /** - * - * @param {string[]} values - */ - getCodeMeaning(values) { - let q = this.getOrQuery( - dictionary.keyword.CodeMeaning, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - addQuery(q) { - let currentCodeModel = this.isModelIncluded(); - if (currentCodeModel) { - currentCodeModel.where = { - ...currentCodeModel.where, - ...q - }; - } else { - this.queryBuilder.includeQueries.push({ - model: DicomCodeModel, - where: { - ...q - }, - as: this.codeTableName, - attributes: [] - }); - } - } -} - -module.exports.DicomCodeQueryBuilder = DicomCodeQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js deleted file mode 100644 index 655ce353..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js +++ /dev/null @@ -1,435 +0,0 @@ -const _ = require("lodash"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { BaseQueryBuilder, StudyQueryBuilder } = require("./querybuilder"); -const { DicomCodeModel } = require("@models/sql/models/dicomCode.model"); -const { DicomContentSqModel } = require("@models/sql/models/dicomContentSQ.model"); -const { VerifyIngObserverSqModel } = require("@models/sql/models/verifyingObserverSQ.model"); -const { PersonNameModel } = require("@models/sql/models/personName.model"); -const sequelize = require("@models/sql/instance"); -const { SeriesQueryBuilder } = require("./seriesQueryBuilder"); -const { Op } = require("sequelize"); - -class InstanceQueryBuilder extends BaseQueryBuilder { - constructor(queryOptions) { - super(queryOptions); - - let conceptNameCodeQueryBuilder = new ConceptNameCodeSqQueryBuilder(this); - this["0040A043.00080100"] = ConceptNameCodeSqQueryBuilder.prototype.getCodeValue.bind(conceptNameCodeQueryBuilder); - this["0040A043.00080102"] = ConceptNameCodeSqQueryBuilder.prototype.getCodingSchemeDesignator.bind(conceptNameCodeQueryBuilder); - this["0040A043.00080103"] = ConceptNameCodeSqQueryBuilder.prototype.getCodingSchemeVersion.bind(conceptNameCodeQueryBuilder); - this["0040A043.00080104"] = ConceptNameCodeSqQueryBuilder.prototype.getCodeMeaning.bind(conceptNameCodeQueryBuilder); - - let contentQueryBuilder = new ContentSqQueryBuilder(this); - this["0040A730.0040A040"] = ContentSqQueryBuilder.prototype.getValueType.bind(contentQueryBuilder); - this["0040A730.0040A010"] = ContentSqQueryBuilder.prototype.getRelationshipType.bind(contentQueryBuilder); - this["0040A730.0040A160"] = ContentSqQueryBuilder.prototype.getTextValue.bind(contentQueryBuilder); - this["0040A730.0040A043.00080100"] = ContentSqQueryBuilder.prototype.getConceptNameCodeValue.bind(contentQueryBuilder); - this["0040A730.0040A043.00080102"] = ContentSqQueryBuilder.prototype.getConceptNameCodingSchemeDesignator.bind(contentQueryBuilder); - this["0040A730.0040A043.00080103"] = ContentSqQueryBuilder.prototype.getConceptNameCodingSchemeVersion.bind(contentQueryBuilder); - this["0040A730.0040A043.00080104"] = ContentSqQueryBuilder.prototype.getConceptNameCodeMeaning.bind(contentQueryBuilder); - this["0040A730.0040A168.00080100"] = ContentSqQueryBuilder.prototype.getConceptCodeValue.bind(contentQueryBuilder); - this["0040A730.0040A168.00080102"] = ContentSqQueryBuilder.prototype.getConceptCodingSchemeDesignator.bind(contentQueryBuilder); - this["0040A730.0040A168.00080103"] = ContentSqQueryBuilder.prototype.getConceptCodingSchemeVersion.bind(contentQueryBuilder); - this["0040A730.0040A168.00080104"] = ContentSqQueryBuilder.prototype.getConceptCodeMeaning.bind(contentQueryBuilder); - - let verifyingObserverQueryBuilder = new VerifyingObserverQueryBuilder(this); - this["0040A073.0040A075"] = VerifyingObserverQueryBuilder.prototype.getName.bind(verifyingObserverQueryBuilder); - this["0040A073.0040A030"] = VerifyingObserverQueryBuilder.prototype.getDateTime.bind(verifyingObserverQueryBuilder); - this["0040A073.0040A027"] = VerifyingObserverQueryBuilder.prototype.getOrganization.bind(verifyingObserverQueryBuilder); - - - let seriesQueryBuilder = new SeriesQueryBuilder(queryOptions); - let seriesQuery = seriesQueryBuilder.build(); - this.includeQueries.push({ - model: sequelize.model("Series"), - attributes: ["x0020000E"], - ...seriesQuery, - required: true - }); - - let instanceUidInParams = _.get(this.queryOptions.requestParams, "instanceUID"); - if (instanceUidInParams) { - this.query = { - x00080018: instanceUidInParams - }; - } - } - - /** - * - * @param {string[]} values - */ - getSOPClassUID(values) { - return this.getOrQuery(dictionary.keyword.SOPClassUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - /** - * - * @param {string[]} values - */ - getSOPInstanceUID(values) { - return this.getOrQuery(dictionary.keyword.SOPInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - /** - * - * @param {string[]} values - */ - getContentDate(values) { - return this.getOrQuery(dictionary.keyword.ContentDate, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - /** - * - * @param {string[]} values - */ - getContentTime(values) { - return this.getOrQuery(dictionary.keyword.ContentTime, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - /** - * - * @param {string[]} values - */ - getInstanceNumber(values) { - return this.getOrQuery(dictionary.keyword.InstanceNumber, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } -} - -class ConceptNameCodeSqQueryBuilder { - constructor(instanceQueryBuilder) { - /** @type {InstanceQueryBuilder} */ - this.instanceQueryBuilder = instanceQueryBuilder; - } - - isModelIncluded() { - return this.instanceQueryBuilder.includeQueries.find(v => v.model.getTableName() === "ConceptNameCodeSQ"); - } - - getCodeValue(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeValue, value); - this.addQuery(q); - } - - getCodingSchemeDesignator(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeDesignator, value); - this.addQuery(q); - } - - getCodingSchemeVersion(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodingSchemeVersion, value); - this.addQuery(q); - } - - getCodeMeaning(value) { - let q = this.instanceQueryBuilder.getStringQuery(dictionary.keyword.CodeMeaning, value); - this.addQuery(q); - } - - addQuery(q) { - let currentModel = this.isModelIncluded(); - if (currentModel) { - currentModel.where = { - ...currentModel.where, - ...q - }; - } else { - this.instanceQueryBuilder.includeQueries.push({ - model: DicomCodeModel, - where: { - ...q - }, - attributes: [] - }); - } - } -} - -class ContentSqQueryBuilder { - constructor(instanceQueryBuilder) { - /** @type {InstanceQueryBuilder} */ - this.instanceQueryBuilder = instanceQueryBuilder; - this.conceptNameCodeInclude = undefined; - } - - isModelIncluded() { - return this.instanceQueryBuilder.includeQueries.find(v => v.model.getTableName() === "DicomContentSQ"); - } - - getConceptNameCodeInclude() { - let currentModel = this.isModelIncluded(); - if (currentModel && currentModel.include) { - return currentModel.include.find(v => v.model.getTableName() === "ConceptNameCode"); - } - return undefined; - } - - getConceptCodeInclude() { - let currentModel = this.isModelIncluded(); - if (currentModel && currentModel.include) { - return currentModel.include.find(v => v.model.getTableName() === "ConceptCode"); - } - return undefined; - } - - getValueType(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.ValueType, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - getTextValue(value) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.TextValue, - value, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - getRelationshipType(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.RelationshipType, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - getConceptNameCodeValue(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodeValue, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptNameCodeQuery(q); - } - - getConceptNameCodingSchemeDesignator(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodingSchemeDesignator, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptNameCodeQuery(q); - } - - getConceptNameCodingSchemeVersion(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodingSchemeVersion, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptNameCodeQuery(q); - } - - getConceptNameCodeMeaning(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodeMeaning, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptNameCodeQuery(q); - } - - getConceptCodeValue(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodeValue, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptCodeQuery(q); - } - - getConceptCodingSchemeDesignator(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodingSchemeDesignator, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptCodeQuery(q); - } - - getConceptCodingSchemeVersion(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.CodingSchemeVersion, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptCodeQuery(q); - } - - getConceptCodeMeaning(values) { - let q = this.getOrQuery( - dictionary.keyword.CodeMeaning, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addConceptCodeQuery(q); - } - - addQuery(q) { - let currentModel = this.isModelIncluded(); - if (!currentModel) { - this.instanceQueryBuilder.includeQueries.push({ - model: DicomContentSqModel, - where: { - [Op.and]: [ - q - ] - }, - attributes: [] - }); - } else { - currentModel.where[Op.and] = { - ...currentModel.where[Op.and], - q - }; - } - } - - addConceptNameCodeQuery(q) { - if (!this.conceptNameCodeInclude) { - this.conceptNameCodeInclude = { - model: DicomCodeModel, - as: "ConceptNameCode", - where: { - [Op.and]: [ - q - ] - }, - attributes: [] - }; - } else { - this.conceptNameCodeInclude.where[Op.and] = { - ...this.conceptNameCodeInclude.where[Op.and], - q - }; - } - - let conceptNameIncluded = this.getConceptNameCodeInclude(); - - if (conceptNameIncluded) { - conceptNameIncluded = this.conceptNameCodeInclude; - } else { - this.addQuery({}); - let currentModel = this.isModelIncluded(); - currentModel.include = currentModel.include ? currentModel.include : []; - conceptNameIncluded = this.getConceptNameCodeInclude(); - currentModel.include.push(this.conceptNameCodeInclude); - } - } - - addConceptCodeQuery(q) { - if (!this.conceptCodeInclude) { - this.conceptCodeInclude = { - model: DicomCodeModel, - as: "ConceptCode", - where: { - ...q - }, - attributes: [] - }; - } else { - this.conceptCodeInclude.where = { - ...this.conceptCodeInclude.where, - ...q - }; - } - - let conceptCodeIncluded = this.getConceptCodeInclude(); - - if (conceptCodeIncluded) { - conceptCodeIncluded = this.conceptCodeInclude; - } else { - this.addQuery({}); - let currentModel = this.isModelIncluded(); - currentModel.include = currentModel.include ? currentModel.include : []; - conceptCodeIncluded = this.getConceptCodeInclude(); - currentModel.include.push(this.conceptCodeInclude); - } - } -} - -class VerifyingObserverQueryBuilder { - constructor(instanceQueryBuilder) { - /** @type {InstanceQueryBuilder} */ - this.instanceQueryBuilder = instanceQueryBuilder; - } - - isModelIncluded() { - return this.instanceQueryBuilder.includeQueries.find(v => v.model.getTableName() === "VerifyingObserverSQ"); - } - - isPersonNameIncluded() { - let currentModel = this.isModelIncluded(); - if (currentModel && currentModel.include) { - return currentModel.include.find(v => v.model.getTableName() === "PersonName"); - } - return false; - } - - getName(values) { - let query = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.VerifyingObserverName, - values, - BaseQueryBuilder.prototype.getPersonNameQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery({}); - let currentModel = this.isModelIncluded(); - currentModel.include = currentModel.include ? currentModel.include : []; - let personNameIncluded = this.isPersonNameIncluded(); - if (!personNameIncluded) { - currentModel.include.push({ - model: PersonNameModel, - where: { - [Op.or]: query[Op.or] - }, - attributes: [] - }); - } - } - - getDateTime(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.VerificationDateTime, - values, - BaseQueryBuilder.prototype.getDateTimeQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - getOrganization(values) { - let q = this.instanceQueryBuilder.getOrQuery( - dictionary.keyword.VerifyingOrganization, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.instanceQueryBuilder) - ); - this.addQuery(q); - } - - addQuery(q) { - let currentModel = this.isModelIncluded(); - if (!currentModel) { - this.instanceQueryBuilder.includeQueries.push({ - model: VerifyIngObserverSqModel, - where: { - [Op.and]: [ - q - ] - }, - attributes: [] - }); - } else { - currentModel.where[Op.and] = [ - ...currentModel.where[Op.and], - q - ]; - } - } -} - -module.exports.InstanceQueryBuilder = InstanceQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js deleted file mode 100644 index 96d962aa..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder.js +++ /dev/null @@ -1,50 +0,0 @@ -const _ = require("lodash"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { BaseQueryBuilder } = require("./querybuilder"); -const sequelize = require("@models/sql/instance"); -const { PersonNameModel } = require("@models/sql/models/personName.model"); -const { Op } = require("sequelize"); - -class PatientQueryBuilder extends BaseQueryBuilder { - constructor(queryOptions) { - super(queryOptions); - } - - getIncludedPersonNameModel() { - if (this.includeQueries.length > 0) { - return this.includeQueries.find(v => v.model.getTableName() === "PersonName"); - } - return undefined; - } - - getPatientName(values) { - let query = this.getOrQuery(dictionary.keyword.PatientName, values, this.getPersonNameQuery.bind(this)); - - let includedPersonNameModel = this.getIncludedPersonNameModel(); - if (!includedPersonNameModel) { - this.includeQueries.push({ - model: PersonNameModel, - required: true, - where: { - [Op.or]: query[Op.or] - } - }); - } - - } - - getPatientID(values) { - return this.getOrQuery(dictionary.keyword.PatientID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - getPatientBirthDate(value) { - return this.getOrQuery(dictionary.keyword.PatientBirthDate, value, BaseQueryBuilder.prototype.getDateQuery.bind(this)); - } - - getIssuerOfPatientID(value) { - return this.getOrQuery(dictionary.keyword.IssuerOfPatientID, value, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - -} - -module.exports.PatientQueryBuilder = PatientQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js deleted file mode 100644 index 7921b927..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ /dev/null @@ -1,17 +0,0 @@ -const { - QueryDicomJsonFactory, - QueryPatientDicomJsonFactory, - QueryStudyDicomJsonFactory, - QuerySeriesDicomJsonFactory, - QueryInstanceDicomJsonFactory -} = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); - -QueryDicomJsonFactory.prototype.getDicomJson = async function () { - return await this.model.getDicomJson(this.queryOptions); -}; - -module.exports.QueryDicomJsonFactory = QueryDicomJsonFactory; -module.exports.QueryPatientDicomJsonFactory = QueryPatientDicomJsonFactory; -module.exports.QueryStudyDicomJsonFactory = QueryStudyDicomJsonFactory; -module.exports.QuerySeriesDicomJsonFactory = QuerySeriesDicomJsonFactory; -module.exports.QueryInstanceDicomJsonFactory = QueryInstanceDicomJsonFactory; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js deleted file mode 100644 index f4222271..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js +++ /dev/null @@ -1,495 +0,0 @@ -const _ = require("lodash"); -const moment = require("moment"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { Op, Sequelize, cast, col } = require("sequelize"); -const { raccoonConfig } = require("@root/config-class"); -const { PersonNameModel } = require("@models/sql/models/personName.model"); -const sequelize = require("@models/sql/instance"); -const { logger } = require("@root/utils/logs/log"); - -class BaseQueryBuilder { - constructor(queryOptions) { - this.queryOptions = queryOptions; - /** @type {import("sequelize").IncludeOptions[]} */ - this.includeQueries = []; - this.bind = []; - this.query = { - [Op.and]: [] - }; - } - - build() { - for (let key in this.queryOptions.query) { - this.getQueryByParam_(key); - } - - let sequelizeQuery = { - where: this.query - }; - - let includesPersonNameQuery = this.getSequelizeIncludePersonNameQuery(); - if (includesPersonNameQuery.length > 0) { - _.set(sequelizeQuery, "include", includesPersonNameQuery); - } - - if (this.bind.length > 0) { - _.set(sequelizeQuery, "bind", this.bind); - } - return sequelizeQuery; - } - - /** - * @private - * @param {string} key - */ - getQueryByParam_(key) { - let value = this.queryOptions.query[key]; - let values = Array.isArray(value) ? value : [value]; - - for (let i = 0; i < values.length; i++) { - let paramValue = values[i]; - let commaValue = this.comma(key, paramValue); - let wildCardValues = commaValue.map(v => this.getWildCardQuery(v[key])); - try { - let query; - if (key.includes(".")) { - query = this[key](wildCardValues); - } else { - query = this[`get${dictionary.tag[key]}`](wildCardValues); - } - - this.query[Op.and] = [ - ...this.query[Op.and], - query - ]; - } catch (e) { - if (e.message.includes("not a function")) break; - logger.error(e); - throw e; - } - - } - } - - getSequelizeIncludePersonNameQuery() { - let includes = []; - - for (let includeQuery of this.includeQueries) { - includes.push(includeQuery); - } - return includes; - } - - comma(key, value) { - let $or = []; - let valueCommaSplit = value.split(","); - for (let i = 0; i < valueCommaSplit.length; i++) { - let obj = {}; - obj[key] = valueCommaSplit[i]; - $or.push(obj); - } - return $or; - } - - /** - * - * @param {string} tag - * @param {string} value - * @returns - */ - getStringQuery(tag, value) { - let queryField = this.getQueryField(tag); - if (value.includes("%") || value.includes("_")) { - return { - [queryField]: { - [Op.like]: value - } - }; - } - return { - [queryField]: value - }; - } - - /** - * - * @param {string} tag - * @param {string[]} values - * @param {(tag: string, values: string[]) => void} queryFn - */ - getOrQuery(tag, values, queryFn) { - if (values.length === 1) { - return queryFn(tag, values[0]); - } - - let or = { - [Op.or]: [] - }; - for (let i = 0; i < values.length; i++) { - let q = queryFn(tag, values[i]); - or[Op.or].push(q); - } - return or; - } - - getStringArrayJsonQuery(tag, value) { - let queryField = this.getQueryField(tag); - if (raccoonConfig.dbConfig.dialect === "postgres") { - return { - [queryField]: { - [Op.contains]: cast(JSON.stringify([value]), "jsonb") - } - }; - } else { - return { - [Op.or]: [Sequelize.where(Sequelize.fn("JSON_CONTAINS", Sequelize.col(queryField), `[${value}]`))] - }; - } - } - - getNumberQuery(tag, value) { - let queryField = this.getQueryField(tag); - return { - [queryField]: Number(value) - }; - } - - /** - * - * @param {string} tag - * @param {string} value - * @returns - */ - getPersonNameQuery(tag, value) { - if (value.includes("%") || value.includes("_")) { - return { - [Op.or]: [ - { - alphabetic: { - [Op.like]: value - } - }, - { - ideographic: { - [Op.like]: value - } - }, - { - phonetic: { - [Op.like]: value - } - } - ] - }; - } - - return { - [Op.or]: [ - { alphabetic: value }, - { ideographic: value }, - { phonetic: value } - ] - }; - } - - /** - * - * @param {string} tag - * @param {string} value - */ - getDateQuery(tag, value) { - let queryField = this.getQueryField(tag); - let dashIndex = value.indexOf("-"); - if (dashIndex === 0) { // -YYYYMMDD - return { - [queryField]: { - [Op.lte]: this.dateStringToSqlDateOnly(value.substring(1)) - } - }; - } else if (dashIndex === value.length - 1) { // YYYYMMDD- - return { - [queryField]: { - [Op.gte]: this.dateStringToSqlDateOnly(value.substring(0, dashIndex)) - } - }; - } else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD - return { - [queryField]: { - [Op.and]: [ - { [Op.gte]: this.dateStringToSqlDateOnly(value.substring(0, dashIndex)) }, - { [Op.lte]: this.dateStringToSqlDateOnly(value.substring(dashIndex + 1)) } - ] - } - }; - } else { // YYYYMMDD - return { - [queryField]: this.dateStringToSqlDateOnly(value) - }; - } - } - - /** - * - * @param {string} tag - * @param {string} value - */ - getTimeQuery(tag, value) { - let queryField = this.getQueryField(tag); - let dashIndex = value.indexOf("-"); - if (dashIndex === 0) { // -HHmmss - return { - [queryField]: { - [Op.lte]: this.timeStringToSqlTimeDecimal(value.substring(1)) - } - }; - } else if (dashIndex === value.length - 1) { // HHmmss- - return { - [queryField]: { - [Op.gte]: this.timeStringToSqlTimeDecimal(value.substring(0, dashIndex)) - } - }; - } else if (dashIndex > 0) { // HHmmss-HHmmss - return { - [queryField]: { - [Op.and]: [ - { [Op.gte]: this.timeStringToSqlTimeDecimal(value.substring(0, dashIndex)) }, - { [Op.lte]: this.timeStringToSqlTimeDecimal(value.substring(dashIndex + 1)) } - ] - } - }; - } else { - return { - [queryField]: this.timeStringToSqlTimeDecimal(value) - }; - } - } - - /** - * - * @param {string} tag - * @param {string} value - */ - getDateTimeQuery(tag, value) { - let queryField = this.getQueryField(tag); - let dashIndex = value.indexOf("-"); - if (dashIndex === 0) { // -YYYYMMDD - return { - [queryField]: { - [Op.lte]: this.dateTimeStringToSqlDateTime(value.substring(1)) - } - }; - } else if (dashIndex === value.length - 1) { // YYYYMMDD- - return { - [queryField]: { - [Op.gte]: this.dateTimeStringToSqlDateTime(value.substring(0, dashIndex)) - } - }; - } else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD - return { - [queryField]: { - [Op.and]: [ - { [Op.gte]: this.dateTimeStringToSqlDateTime(value.substring(0, dashIndex)) }, - { [Op.lte]: this.dateTimeStringToSqlDateTime(value.substring(dashIndex + 1)) } - ] - } - }; - } else { // YYYYMMDD - return { - [queryField]: this.dateTimeStringToSqlDateTime(value) - }; - } - } - - dateStringToSqlDateOnly(value) { - return moment(value, "YYYYMMDD").format("YYYY-MM-DD"); - } - - dateTimeStringToSqlDateTime(value) { - return moment(value, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString(); - } - - /** - * - * @param {string} timeStr - */ - getTimePadding(timeStr) { - let hhmmssStr = timeStr.padEnd(6, "0"); - if (timeStr.length === 5) { - hhmmssStr.padStart(6, "0"); - } - return hhmmssStr; - } - - - timeStringToSqlTimeDecimal(value) { - let hhmmssStr = this.getTimePadding(value); - if (hhmmssStr.includes(".")) { - let [timeStr, millionthSecondStr] = hhmmssStr.split("."); - hhmmssStr = this.getTimePadding(timeStr) + "." + millionthSecondStr; - } - return parseFloat(hhmmssStr); - } - - /** - * - * @param {string} value - * @returns - */ - getWildCardQuery(value) { - let wildCardIndex = value.indexOf("*"); - let questionIndex = value.indexOf("?"); - - if (wildCardIndex >= 0 || questionIndex >= 0) { - value = value.replace(/\*/gm, "%"); - value = value.replace(/\?/gm, "_"); - } - - return value; - } - - getWildCardRegexString(value) { - let wildCardIndex = value.indexOf("%"); - let questionIndex = value.indexOf("_"); - - if (wildCardIndex >= 0 || questionIndex >= 0) { - value = value.replace(/%/gm, ".*"); - value = value.replace(/_/gm, "."); - value = value.replace(/\^/gm, "\\^"); - value = "^" + value; - } - - return value; - } - - /** - * - * @param {*} q - * @see {@link https://stackoverflow.com/questions/60598225/how-to-merge-javascript-object-containing-symbols "How to merge javascript object containing symbols?"} - */ - mergeQuery(q) { - _.mergeWith(this.query, q, (a, b) => { - if (!_.isObject(b)) return b; - - return Array.isArray(a) ? [...a, ...b] : { ...a, ...b }; - }); - } - - /** - * - * @param {string} tag - * @returns - */ - getQueryField(tag) { - return /^[0-9a-zA-Z]{8}$/.test(tag.substring(0, 8)) ? `x${tag}` : tag; - } -} - -class StudyQueryBuilder extends BaseQueryBuilder { - constructor(queryOptions) { - super(queryOptions); - - let studyInstanceUidInParams = _.get(this.queryOptions.requestParams, "studyUID"); - if (studyInstanceUidInParams) { - this.query = { - x0020000D: studyInstanceUidInParams - }; - } - } - - getIncludedPatientModel() { - if (this.includeQueries.length > 0) { - return this.includeQueries.find(v => v.model.getTableName() === "Patient"); - } - return undefined; - } - - getIncludedPersonNameModelInPatient() { - let includedPatientModel = this.getIncludedPatientModel(); - if (includedPatientModel) { - return includedPatientModel.include.find(v => v.model.getTableName() === "PersonName"); - } - return undefined; - } - - getIncludedPersonNameModel() { - if (this.includeQueries.length > 0) { - return this.includeQueries.find(v => v.model.getTableName() === "PersonName"); - } - return undefined; - } - - - getStudyInstanceUID(values) { - return this.getOrQuery(dictionary.keyword.StudyInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - getPatientName(values) { - let query = this.getOrQuery(dictionary.keyword.PatientName, values, BaseQueryBuilder.prototype.getPersonNameQuery.bind(this)); - - let includedPatientModel = this.getIncludedPatientModel(); - if (!includedPatientModel) { - this.includeQueries.push({ - model: sequelize.model("Patient"), - include: [{ - model: sequelize.model("PersonName"), - where: { - [Op.or]: query[Op.or] - }, - required: true - }], - required: true - }); - } - } - - getPatientID(values) { - return this.getOrQuery(dictionary.keyword.PatientID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - getStudyDate(values) { - return this.getOrQuery(dictionary.keyword.StudyDate, values, BaseQueryBuilder.prototype.getDateQuery.bind(this)); - } - - getStudyTime(values) { - return this.getOrQuery(dictionary.keyword.StudyTime, values, BaseQueryBuilder.prototype.getTimeQuery.bind(this)); - } - - getAccessionNumber(values) { - return this.getOrQuery(dictionary.keyword.AccessionNumber, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - getModalitiesInStudy(values) { - let stringQuery = this.getOrQuery(dictionary.keyword.Modality, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - this.includeQueries.push({ - model: sequelize.model("Series"), - where: { - ...stringQuery - }, - attributes: [] - }); - } - - getReferringPhysicianName(values) { - let query = this.getOrQuery(dictionary.keyword.ReferringPhysicianName, values, BaseQueryBuilder.prototype.getPersonNameQuery.bind(this)); - let includedPersonNameModel = this.getIncludedPersonNameModel(); - if (!includedPersonNameModel) { - this.includeQueries.push({ - model: PersonNameModel, - where: { - [Op.or]: [ - ...query[Op.or] - ] - }, - required: true - }); - } - } - - getStudyID(values) { - return this.getOrQuery(dictionary.keyword.StudyID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } -} - - -module.exports.BaseQueryBuilder = BaseQueryBuilder; -module.exports.StudyQueryBuilder = StudyQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js deleted file mode 100644 index 779a7692..00000000 --- a/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js +++ /dev/null @@ -1,209 +0,0 @@ -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { BaseQueryBuilder, StudyQueryBuilder } = require("./querybuilder"); -const { Op, Sequelize } = require("sequelize"); -const { raccoonConfig } = require("@root/config-class"); -const _ = require("lodash"); -const { PersonNameModel } = require("@models/sql/models/personName.model"); -const sequelize = require("@models/sql/instance"); -const { SeriesRequestAttributesModel } = require("@models/sql/models/seriesRequestAttributes.model"); - -class SeriesQueryBuilder extends BaseQueryBuilder { - constructor(queryOptions) { - super(queryOptions); - let seriesRequestAttributeSequence = new SeriesRequestAttributeSequence(this); - this["00400275.00080050"] = SeriesRequestAttributeSequence.prototype.getAccessionNumber.bind(seriesRequestAttributeSequence); - this["00400275.00080051.00400031"] = SeriesRequestAttributeSequence.prototype.getIssuerLocalNameSpaceEntityID.bind(seriesRequestAttributeSequence); - this["00400275.00080051.00400032"] = SeriesRequestAttributeSequence.prototype.getIssuerUniversalEntityID.bind(seriesRequestAttributeSequence); - this["00400275.00080051.00400033"] = SeriesRequestAttributeSequence.prototype.getIssuerUniversalEntityIDType.bind(seriesRequestAttributeSequence); - this["00400275.00321033"] = SeriesRequestAttributeSequence.prototype.getRequestingService.bind(seriesRequestAttributeSequence); - this["00400275.00401001"] = SeriesRequestAttributeSequence.prototype.getRequestedProcedureID.bind(seriesRequestAttributeSequence); - this["00400275.0020000D"] = SeriesRequestAttributeSequence.prototype.getStudyInstanceUID.bind(seriesRequestAttributeSequence); - - let studyQueryBuilder = new StudyQueryBuilder(queryOptions); - let studyQuery = studyQueryBuilder.build(); - this.includeQueries.push({ - model: sequelize.model("Study"), - attributes: ["x0020000D"], - ...studyQuery, - required: true - }); - - let seriesInstanceUidInParams = _.get(this.queryOptions.requestParams, "seriesUID"); - if (seriesInstanceUidInParams) { - this.query = { - x0020000E: seriesInstanceUidInParams - }; - } - } - - getIncludedPerformingPhysicianNameModel() { - if (this.includeQueries.length > 0) { - return this.includeQueries.find(v => _.get(v, "as", "") === "performingPhysicianName"); - } - return undefined; - } - - getIncludedOperatorsNameModel() { - if (this.includeQueries.length > 0) { - return this.includeQueries.find(v => _.get(v, "as", "") === "operatorsName"); - } - return undefined; - } - getSeriesDate(values) { - return this.getOrQuery(dictionary.keyword.SeriesDate, values, BaseQueryBuilder.prototype.getDateQuery.bind(this)); - } - getModality(values) { - return this.getOrQuery(dictionary.keyword.Modality, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - getSeriesDescription(values) { - return this.getOrQuery(dictionary.keyword.SeriesDescription, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - getPersonNameJsonArrayQuery(tag, value) { - if (raccoonConfig.dbConfig.dialect === "postgres") { - value = this.getWildCardRegexString(value); - return { - [Op.or]: [ - Sequelize.literal(`"x${tag}" @? '$[*].Alphabetic ? (@ like_regex "${value}" flag "is")'`) - ] - }; - } - throw new Error("Not implemented"); - } - - getPerformingPhysicianName(values) { - let query = this.getOrQuery(dictionary.keyword.PerformingPhysicianName, values, this.getPersonNameQuery.bind(this)); - let includedPerformingPhysicianNameModel = this.getIncludedPerformingPhysicianNameModel(); - if (!includedPerformingPhysicianNameModel) { - this.includeQueries.push({ - model: PersonNameModel, - as: "performingPhysicianName", - where: { - [Op.or]: query[Op.or] - }, - attributes: [] - }); - } - - } - - getOperatorsName(values) { - let query = this.getOrQuery(dictionary.keyword.OperatorsName, values, this.getPersonNameQuery.bind(this)); - this.includeQueries.push({ - model: PersonNameModel, - as: "operatorsName", - where: { - [Op.or]: query[Op.or] - }, - attributes: [] - }); - } - - getSeriesNumber(values) { - return this.getOrQuery(dictionary.keyword.SeriesNumber, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - getSeriesInstanceUID(values) { - return this.getOrQuery(dictionary.keyword.SeriesInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - -} - -class SeriesRequestAttributeSequence { - constructor(seriesQueryBuilder) { - /** @type {SeriesQueryBuilder} */ - this.seriesQueryBuilder = seriesQueryBuilder; - } - isModelIncluded() { - return this.seriesQueryBuilder.includeQueries.find(v => v.model.getTableName() === "SeriesRequestAttributes"); - } - getAccessionNumber(values) { - let q = this.seriesQueryBuilder.getOrQuery( - dictionary.keyword.AccessionNumber, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) - ); - this.addQuery(q); - } - - getIssuerLocalNameSpaceEntityID(values) { - let q = this.seriesQueryBuilder.getOrQuery( - "00080051_x00400031", - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) - ); - this.addQuery(q); - } - - getIssuerUniversalEntityID(values) { - let q = this.seriesQueryBuilder.getOrQuery( - "00080051_x00400032", - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) - ); - this.addQuery(q); - } - - getIssuerUniversalEntityIDType(values) { - let q = this.seriesQueryBuilder.getOrQuery( - "00080051_x00400033", - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) - ); - this.addQuery(q); - } - - /** - * - * @param {string[]} values - The values to be used in the query generation. - */ - getRequestingService(values) { - let q = this.seriesQueryBuilder.getOrQuery( - dictionary.keyword.RequestingService, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) - ); - this.addQuery(q); - } - - getRequestedProcedureID(values) { - let q = this.seriesQueryBuilder.getOrQuery( - dictionary.keyword.RequestedProcedureID, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) - ); - this.addQuery(q); - } - - getStudyInstanceUID(values) { - let q = this.seriesQueryBuilder.getOrQuery( - dictionary.keyword.StudyInstanceUID, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this.seriesQueryBuilder) - ); - this.addQuery(q); - } - - addQuery(q) { - let currentModel = this.isModelIncluded(); - if (currentModel) { - currentModel.where[Op.and] = [ - ...currentModel.where[Op.and], - q - ]; - } else { - this.seriesQueryBuilder.includeQueries.push({ - model: SeriesRequestAttributesModel, - where: { - [Op.and]: [ - q - ] - }, - attributes: [] - }); - } - } - -} - -module.exports.SeriesQueryBuilder = SeriesQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js deleted file mode 100644 index 915915b1..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ /dev/null @@ -1,76 +0,0 @@ -const _ = require("lodash"); -const { DicomJsonModel } = require("@dicom-json-model"); -const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); -const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription.model"); -const { UpsSubscriptionModel } = require("@dbModels/upsSubscription.model"); -const { WorkItemModel } = require("@dbModels/workitems.model"); -const { BaseWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/base-workItem.service"); -const { UpsQueryBuilder } = require("./query/upsQueryBuilder"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); -class SqlBaseWorkItemService extends BaseWorkItemService { - - constructor(req, res) { - super(req, res); - } - - async isAeTileSubscribed(aeTitle) { - let subscription = await UpsSubscriptionModel.findOne({ - where: { - aeTitle: aeTitle - } - }); - - if (!subscription) - return false; - - return subscription.subscribed === SUBSCRIPTION_STATE.SUBSCRIBED_LOCK || - subscription.subscribed === SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK; - } - - async getGlobalSubscriptionsCursor() { - return UpsGlobalSubscriptionModel.cursor({}); - } - - /** - * @param {DicomJsonModel} workItem - */ - async getHitGlobalSubscriptions(workItem) { - let globalSubscriptionsCursor = await this.getGlobalSubscriptionsCursor(); - let hitGlobalSubscriptions = []; - let globalSubscription = await globalSubscriptionsCursor.next(); - while (globalSubscription) { - if (!globalSubscription.queryKeys) { - hitGlobalSubscriptions.push(globalSubscription); - } else { - let query = convertAllQueryToDicomTag(globalSubscription.queryKeys, false); - _.set(query, "upsInstanceUID", workItem.dicomJson.upsInstanceUID); - let queryOptions = { - query: query - }; - let upsQueryBuilder = new UpsQueryBuilder(queryOptions); - let dbQuery = upsQueryBuilder.build(); - let count = await WorkItemModel.count({ - ...dbQuery - }); - if (count > 0) - hitGlobalSubscriptions.push(globalSubscription); - } - globalSubscription = await globalSubscriptionsCursor.next(); - } - return hitGlobalSubscriptions; - } - - /** - * - * @param {DicomJsonModel} workItem - * @returns - */ - async getHitSubscriptions(workItem) { - let hitSubscriptions = await workItem.dicomJson.getUpsSubscriptions(); - - return hitSubscriptions; - } - -} - -module.exports.BaseWorkItemService = SqlBaseWorkItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js b/api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js deleted file mode 100644 index e22027b9..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/cancel.service.js +++ /dev/null @@ -1,26 +0,0 @@ -const _ = require("lodash"); -const { WorkItemModel } = require("@models/sql/models/workitems.model"); -const { CancelWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/cancel.service"); - -class SqlCancelWorkItemService extends CancelWorkItemService { - - /** - * - * @param {import("express").Request} req - * @param {import("express").Response} res - */ - constructor(req, res) { - super(req, res); - this.upsInstanceUID = this.request.params.workItem; - this.workItem = null; - this.requestWorkItem = /** @type {Object[]} */(this.request.body).pop(); - } - - async initWorkItem() { - this.workItem = await WorkItemModel.findOneWorkItemDicomJsonModel(this.upsInstanceUID); - } - -} - - -module.exports.CancelWorkItemService = SqlCancelWorkItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js deleted file mode 100644 index 3895cdb9..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ /dev/null @@ -1,56 +0,0 @@ -const { WorkItemModel } = require("@dbModels/workitems.model"); -const { DicomJsonModel } = require("@models/DICOM/dicom-json-model"); -const { ChangeWorkItemStateService } = require("@root/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service"); -const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); - -class SqlChangeWorkItemStateService extends ChangeWorkItemStateService { - /** - * - * @param {import('express').Request} req - * @param {import('express').Response} res - */ - constructor(req, res) { - super(req, res); - } - - async changeWorkItemState() { - let foundWorkItem = await WorkItemModel.findOneWorkItemByUpsInstanceUID(this.request.params.workItem); - this.workItem = foundWorkItem.toDicomJsonModel(); - - this.workItemState = this.workItem.getString("00741000"); - this.workItemTransactionUID = this.workItem.getString("00081195"); - let requestState = this.requestState.getString("00741000"); - - if (requestState === "IN PROGRESS") { - this.inProgressChange(); - } else if (requestState === "CANCELED") { - this.cancelChange(); - } else if (requestState === "COMPLETED") { - this.completeChange(); - } - - await foundWorkItem.changeWorkItemState(this.requestState); - await foundWorkItem.reload(); - // TODO: change work item state event - this.triggerUpsChangeStateEvent(foundWorkItem); - } - - /** - * - * @param {WorkItemModel} updatedWorkItem - * @returns - */ - async triggerUpsChangeStateEvent(updatedWorkItem) { - let updatedWorkItemDicomJsonModelObj = updatedWorkItem.toDicomJsonModel(); - - let hitSubscriptions = await this.getHitSubscriptions(new DicomJsonModel(updatedWorkItem)); - - if (hitSubscriptions.length === 0) return; - - let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, updatedWorkItemDicomJsonModelObj.dicomJson.upsInstanceUID, this.stateReportOf(updatedWorkItemDicomJsonModelObj), hitSubscriptionAeTitleArray); - this.triggerUpsEvents(); - } -} - -module.exports.ChangeWorkItemStateService = SqlChangeWorkItemStateService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js deleted file mode 100644 index 7a64639a..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ /dev/null @@ -1,39 +0,0 @@ -const { PatientModel } = require("@dbModels/patient.model"); -const { UIDUtils } = require("@dcm4che/util/UIDUtils"); -const { WorkItemModel } = require("@dbModels/workitems.model"); -const { CreateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/create-workItem.service"); -const { get, set } = require("lodash"); -const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); -const { PatientPersistentObject } = require("@models/sql/po/patient.po"); - -class SqlCreateWorkItemService extends CreateWorkItemService { - constructor(req, res) { - super(req, res); - } - - async createUps() { - let uid = get(this.request, "query.workitem", - await UIDUtils.createUID() - ); - await this.dataAdjustBeforeCreatingUps(uid); - await this.validateWorkItem(uid); - - let patient = await PatientModel.updateOrCreatePatient(this.requestWorkItem.dicomJson); - let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); - let savedWorkItem = await workItem.save(); - - this.triggerCreateEvent(savedWorkItem); - - return workItem; - } - - async isUpsExist(uid) { - return await WorkItemModel.findOne({ - where: { - upsInstanceUID: uid - } - }); - } -} - -module.exports.CreateWorkItemService = SqlCreateWorkItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js deleted file mode 100644 index 87359185..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ /dev/null @@ -1,39 +0,0 @@ -const { cloneDeep } = require("lodash"); -const { GetWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/get-workItem.service"); -const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); -const { WorkItemModel } = require("@models/sql/models/workitems.model"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); - -class SqlGetWorkItemService extends GetWorkItemService { - constructor(req, res) { - super(req, res); - } - - async getUps() { - let queryOptions = { - query: this.query, - skip: this.skip_, - limit: this.limit_, - requestParams: this.request.params - }; - - let docs = await WorkItemModel.getDicomJson(queryOptions); - - return this.adjustDocs(docs); - } - - initQuery_() { - let query = cloneDeep(this.request.query); - let queryKeys = Object.keys(query).sort(); - for (let i = 0; i < queryKeys.length; i++) { - let queryKey = queryKeys[i]; - if (!query[queryKey]) delete query[queryKey]; - } - - this.query = convertAllQueryToDicomTag(query, false); - } -} - -SqlGetWorkItemService.prototype.initQuery_ = QidoRsService.prototype.initQuery_; - -module.exports.GetWorkItemService = SqlGetWorkItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js b/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js deleted file mode 100644 index 8fa740ca..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js +++ /dev/null @@ -1,229 +0,0 @@ -const sequelize = require("@models/sql/instance"); -const { PatientQueryBuilder } = require("../../../QIDO-RS/service/patientQueryBuilder"); -const { BaseQueryBuilder } = require("../../../QIDO-RS/service/querybuilder"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { DicomCodeQueryBuilder } = require("../../../QIDO-RS/service/dicomCodeQueryBuilder"); -const { get } = require("lodash"); - -class UpsQueryBuilder extends BaseQueryBuilder { - - constructor(queryOptions) { - super(queryOptions); - - let patientQueryBuilder = new PatientQueryBuilder(queryOptions); - let patientQuery = patientQueryBuilder.build(); - this.includeQueries.push({ - model: sequelize.model("Patient"), - attributes: ["x00100020"], - ...patientQuery, - required: true - }); - - this.createCodeQueries(dictionary.keyword.ScheduledStationNameCodeSequence); - this.createCodeQueries(dictionary.keyword.ScheduledStationClassCodeSequence); - this.createCodeQueries(dictionary.keyword.ScheduledStationGeographicLocationCodeSequence); - this.createCodeQueries(dictionary.keyword.HumanPerformerCodeSequence); - this.createCodeQueries(dictionary.keyword.ScheduledWorkitemCodeSequence); - this.createScheduledHumanPerformersSequenceQueries(); - this.createIssuerOfAdmissionIdSequenceQueries(); - - let upsInstanceUIDInParams = get(this.queryOptions.requestParams, "workItem"); - if (upsInstanceUIDInParams) { - this.query = { - upsInstanceUID: upsInstanceUIDInParams - }; - } - } - - /** - * - * @param {string[]} values - * @returns - */ - getSOPInstanceUID(values) { - return this.getOrQuery(dictionary.keyword.SOPInstanceUID, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - /** - * - * @param {string[]} values - * @returns - */ - getScheduledProcedureStepPriority(values) { - return this.getOrQuery( - dictionary.keyword.ScheduledProcedureStepPriority, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - /** - * - * @param {string[]} values - * @returns - */ - getScheduledProcedureStepModificationDateTime(values) { - return this.getOrQuery( - dictionary.keyword.ScheduledProcedureStepModificationDateTime, - values, - BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) - ); - } - - /** - * - * @param {string[]} values - * @returns - */ - getProcedureStepLabel(values) { - return this.getOrQuery(dictionary.keyword.ProcedureStepLabel, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - /** - * - * @param {string[]} values - * @returns - */ - getWorklistLabel(values) { - return this.getOrQuery(dictionary.keyword.WorklistLabel, values, BaseQueryBuilder.prototype.getStringQuery.bind(this)); - } - - /** - * - * @param {string[]} values - * @returns - */ - getScheduledProcedureStepStartDateTime(values) { - return this.getOrQuery( - dictionary.keyword.ScheduledProcedureStepStartDateTime, - values, - BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) - ); - } - - /** - * - * @param {string[]} values - * @returns - */ - getExpectedCompletionDateTime(values) { - return this.getOrQuery( - dictionary.keyword.ExpectedCompletionDateTime, - values, - BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) - ); - } - - /** - * - * @param {string[]} values - * @returns - */ - getScheduledProcedureStepExpirationDateTime(values) { - return this.getOrQuery( - dictionary.keyword.ScheduledProcedureStepExpirationDateTime, - values, - BaseQueryBuilder.prototype.getDateTimeQuery.bind(this) - ); - } - - /** - * - * @param {string[]} values - * @returns - */ - getInputReadinessState(values) { - return this.getOrQuery( - dictionary.keyword.InputReadinessState, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - /** - * - * @param {string[]} values - * @returns - */ - getAdmissionID(values) { - return this.getOrQuery( - dictionary.keyword.AdmissionID, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - /** - * - * @param {string[]} values - * @returns - */ - getProcedureStepState(values) { - return this.getOrQuery( - dictionary.keyword.ProcedureStepState, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - } - - createCodeQueries(tag) { - let dicomCodeQueryBuilder = new DicomCodeQueryBuilder(this, dictionary.tag[tag]); - this[`${tag}.00080100`] = (values) => dicomCodeQueryBuilder.getCodeValue(values); - this[`${tag}.00080102`] = (values) => dicomCodeQueryBuilder.getCodingSchemeDesignator(values); - this[`${tag}.00080103`] = (values) => dicomCodeQueryBuilder.getCodingSchemeVersion(values); - this[`${tag}.00080104`] = (values) => dicomCodeQueryBuilder.getCodeMeaning(values); - } - - createIssuerOfAdmissionIdSequenceQueries() { - this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.LocalNamespaceEntityID}`] = (values) => { - return this.getOrQuery( - `${dictionary.keyword.IssuerOfAdmissionIDSequence}_x${dictionary.keyword.LocalNamespaceEntityID}`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityID}`] = (values) => { - return this.getOrQuery( - `${dictionary.keyword.IssuerOfAdmissionIDSequence}_x${dictionary.keyword.UniversalEntityID}`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - - this[`${dictionary.keyword.IssuerOfAdmissionIDSequence}.${dictionary.keyword.UniversalEntityIDType}`] = (values) => { - return this.getOrQuery( - `${dictionary.keyword.IssuerOfAdmissionIDSequence}_x${dictionary.keyword.UniversalEntityIDType}`, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - } - - createScheduledHumanPerformersSequenceQueries() { - this[`${dictionary.keyword.ScheduledHumanPerformersSequence}.${dictionary.keyword.HumanPerformerName}`] = (values) => { - let q = this.getOrQuery( - `${dictionary.keyword.ScheduledHumanPerformersSequence}.${dictionary.keyword.HumanPerformerName}`, - values, - BaseQueryBuilder.prototype.getPersonNameQuery.bind(this) - ); - this.includeQueries.push({ - model: sequelize.model("PersonName"), - as: dictionary.tag["00404037"], - where: { - ...q - }, - attributes: [] - }); - }; - this[`${dictionary.keyword.ScheduledHumanPerformersSequence}.${dictionary.keyword.HumanPerformerOrganization}`] = (values) => { - return this.getOrQuery( - dictionary.keyword.HumanPerformerOrganization, - values, - BaseQueryBuilder.prototype.getStringQuery.bind(this) - ); - }; - } -} - -module.exports.UpsQueryBuilder = UpsQueryBuilder; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js deleted file mode 100644 index c5e80c04..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ /dev/null @@ -1,147 +0,0 @@ -const _ = require("lodash"); -const { DicomJsonModel } = require("@dicom-json-model"); -const { UpsSubscriptionModel } = require("@dbModels/upsSubscription.model"); -const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription.model"); -const { - DicomWebServiceError, - DicomWebStatusCodes -} = require("@error/dicom-web-service"); -const { SUBSCRIPTION_STATE, SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); -const { SubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/subscribe.service"); -const { WorkItemModel } = require("@models/sql/models/workitems.model"); -const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); - -class SqlSubscribeService extends SubscribeService { - - /** - * - * @param {import("express").Request} req - * @param {import("express").Response} res - */ - constructor(req, res) { - super(req, res); - } - - async create() { - - if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID || - this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID) { - - this.query = convertAllQueryToDicomTag(this.request.query, false); - await this.createOrUpdateGlobalSubscription(); - } else { - let workItem = await WorkItemModel.findOneWorkItemByUpsInstanceUID(this.upsInstanceUID); - let workItemDicomJsonModel = workItem.toDicomJsonModel(); - await this.createOrUpdateSubscription(workItem); - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, this.upsInstanceUID, this.stateReportOf(workItemDicomJsonModel), [this.subscriberAeTitle]); - } - - await this.triggerUpsEvents(); - } - - //#region Subscription - async findOneSubscription() { - return await UpsSubscriptionModel.findOne({ - where: { - aeTitle: this.subscriberAeTitle - } - }); - } - - /** - * - * @param {DicomJsonModel} workItem - * @returns - */ - async createOrUpdateSubscription(workItem) { - let subscription = await this.findOneSubscription(); - let subscribed = this.deletionLock ? SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK : SUBSCRIPTION_STATE.SUBSCRIBED_LOCK; - await this.updateWorkItemSubscription(workItem, subscribed); - if (!subscription) { - // Create - let subscriptionObj = await UpsSubscriptionModel.create({ - aeTitle: this.subscriberAeTitle, - isDeletionLock: this.deletionLock, - subscribed: subscribed - }); - - await workItem.addUpsSubscription(subscriptionObj); - return subscriptionObj; - } else { - // Update - subscription.isDeletionLock = this.deletionLock; - subscription.subscribed = subscribed; - if (!await workItem.hasUpsSubscription(subscription)) { - workItem.addUpsSubscription(subscription); - } - let updatedSubscription = await subscription.save(); - return updatedSubscription; - } - } - - async updateWorkItemSubscription(workItem, subscription) { - workItem.subscribed = subscription; - await workItem.save(); - } - //#endregion - - //#region Global Subscriptions - async createOrUpdateGlobalSubscription() { - let subscribed = this.deletionLock ? SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK : SUBSCRIPTION_STATE.SUBSCRIBED_LOCK; - let subscription = await this.findOneGlobalSubscription(); - if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID) this.query = undefined; - if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID && _.isEmpty(this.query)) { - throw new DicomWebServiceError( - DicomWebStatusCodes.InvalidArgumentValue, - `Missing "filter", The Filtered Worklist Subscription must have "filter"`, - 400 - ); - } - if (!subscription) { - //Create - let subscriptionObj = UpsGlobalSubscriptionModel.build({ - aeTitle: this.subscriberAeTitle, - isDeletionLock: this.deletionLock, - subscribed: subscribed, - queryKeys: this.query - }); - - let createdSubscription = await subscriptionObj.save(); - } else { - //Update - subscription.isDeletionLock = this.deletionLock; - subscription.subscribed = subscribed; - subscription.queryKeys = this.query; - subscription.changed("queryKeys"); - await subscription.save(); - } - - let notSubscribedWorkItems = await this.findNotSubscribedWorkItems(); - for(let notSubscribedWorkItem of notSubscribedWorkItems) { - await this.createOrUpdateSubscription(notSubscribedWorkItem); - - this.addUpsEvent(UPS_EVENT_TYPE.StateReport, notSubscribedWorkItem.upsInstanceUID, this.stateReportOf(notSubscribedWorkItem.toDicomJsonModel()), [this.subscriberAeTitle]); - } - } - - async findOneGlobalSubscription() { - return await UpsGlobalSubscriptionModel.findOne({ - where: { - aeTitle: this.subscriberAeTitle - } - }); - } - //#endregion - - async findNotSubscribedWorkItems() { - return await WorkItemModel.findAll({ - where: { - subscribed: SUBSCRIPTION_STATE.NOT_SUBSCRIBED - } - }) || []; - } -} - - -module.exports.SubscribeService = SqlSubscribeService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js b/api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js deleted file mode 100644 index 2a0a24ea..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js +++ /dev/null @@ -1,53 +0,0 @@ -const _ = require("lodash"); -const { - DicomWebServiceError, - DicomWebStatusCodes -} = require("@error/dicom-web-service"); -const { SuspendSubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service"); -const { UpsGlobalSubscriptionModel } = require("@models/sql/models/upsGlobalSubscription.model"); - -class SqlSuspendSubscribeService extends SuspendSubscribeService { - - /** - * - * @param {import("express").Request} req - * @param {import("express").Response} res - */ - constructor(req, res) { - super(req, res); - } - - async delete() { - - if (!(await this.isGlobalSubscriptionExist())) { - throw new DicomWebServiceError( - DicomWebStatusCodes.ProcessingFailure, - "The target Subscription was not found.", - 404 - ); - } - - await this.deleteGlobalSubscription(); - - } - - async deleteGlobalSubscription() { - await UpsGlobalSubscriptionModel.destroy({ - where: { - aeTitle: this.subscriberAeTitle - } - }); - } - - async isGlobalSubscriptionExist() { - return await UpsGlobalSubscriptionModel.destroy({ - where: { - aeTitle: this.subscriberAeTitle - } - }) > 0; - } - -} - - -module.exports.SuspendSubscribeService = SqlSuspendSubscribeService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js deleted file mode 100644 index cafc5d37..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ /dev/null @@ -1,105 +0,0 @@ -const _ = require("lodash"); -const { DicomJsonModel } = require("@dicom-json-model"); -const { DicomCode } = require("@models/DICOM/code"); -const { - DicomWebServiceError, - DicomWebStatusCodes -} = require("@error/dicom-web-service"); -const { SUBSCRIPTION_FIXED_UIDS } = require("@models/DICOM/ups"); -const { UnSubscribeService } = require("@root/api/dicom-web/controller/UPS-RS/service/unsubscribe.service"); -const { WorkItemModel } = require("@models/sql/models/workitems.model"); -const { UpsSubscriptionModel } = require("@models/sql/models/upsSubscription.model"); -const { UpsGlobalSubscriptionModel } = require("@models/sql/models/upsGlobalSubscription.model"); - -class SqlUnSubscribeService extends UnSubscribeService { - - /** - * - * @param {import("express").Request} req - * @param {import("express").Response} res - */ - constructor(req, res) { - super(req, res); - } - - async delete() { - - if (this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.GlobalUID || - this.upsInstanceUID === SUBSCRIPTION_FIXED_UIDS.FilteredGlobalUID) { - - if (!(await this.isGlobalSubscriptionExist())) { - throw new DicomWebServiceError( - DicomWebStatusCodes.ProcessingFailure, - "The target Subscription was not found.", - 404 - ); - } - - await this.deleteGlobalSubscription(); - - } else { - let workItem = await WorkItemModel.findOneWorkItemByUpsInstanceUID(this.upsInstanceUID); - - if (!(await this.isSubscriptionExist())) { - throw new DicomWebServiceError( - DicomWebStatusCodes.ProcessingFailure, - "The target Subscription was not found.", - 404 - ); - } - - await this.deleteSubscription(workItem); - } - - } - - /** - * - * @param {WorkItemModel} workItem - */ - async deleteSubscription(workItem) { - let subscription = await UpsSubscriptionModel.findOne({ - where: { - aeTitle: this.subscriberAeTitle - } - }); - await subscription.removeUPSWorkItem(workItem); - } - - async deleteGlobalSubscription() { - - await Promise.all([ - UpsSubscriptionModel.destroy({ - where: { - aeTitle: this.subscriberAeTitle - } - }), - UpsGlobalSubscriptionModel.destroy({ - where: { - aeTitle: this.subscriberAeTitle - } - }) - ]); - - } - - async isSubscriptionExist() { - return await UpsSubscriptionModel.count({ - where: { - aeTitle: this.subscriberAeTitle - } - }) > 0; - } - - async isGlobalSubscriptionExist() { - return await UpsGlobalSubscriptionModel.count({ - where: { - aeTitle: this.subscriberAeTitle - } - }) > 0; - } - -} - - -module.exports.UnSubscribeService = SqlUnSubscribeService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js deleted file mode 100644 index f89389dc..00000000 --- a/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ /dev/null @@ -1,73 +0,0 @@ -const { WorkItemModel } = require("@dbModels/workitems.model"); -const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { DicomJsonModel } = require("@dicom-json-model"); -const { UpdateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/update-workItem.service"); -const { UpsWorkItemPersistentObject } = require("@models/sql/po/upsWorkItem.po"); -const { set, get } = require("lodash"); -const { PatientModel } = require("@models/sql/models/patient.model"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { UPS_EVENT_TYPE } = require("@root/api/dicom-web/controller/UPS-RS/service/workItem-event"); - -class SqlUpdateWorkItemService extends UpdateWorkItemService { - constructor(req, res) { - super(req, res); - this.transactionUID = this.requestWorkItem.getString("00081195"); - set(this.requestWorkItem.dicomJson, "upsInstanceUID", this.request.params.workItem); - } - - async updateUps() { - this.workItem = await WorkItemModel.findOneWorkItemDicomJsonModel(this.request.params.workItem); - await this.checkRequestUpsIsValid(); - this.adjustRequestWorkItem(); - - let patient = await PatientModel.updateOrCreatePatient(this.requestWorkItem.dicomJson); - let workItem = new UpsWorkItemPersistentObject(this.requestWorkItem.dicomJson, patient); - let savedWorkItem = await workItem.save(); - - this.triggerUpdateWorkItemEvent(savedWorkItem); - } - - /** - * replace not allowed updating attribute in request work item - */ - adjustRequestWorkItem() { - for (let i = 0; i < UpdateWorkItemService.notAllowedAttributes.length; i++) { - let notAllowedAttr = UpdateWorkItemService.notAllowedAttributes[i]; - let originalValueOfNotAllowedAttr = get(this.workItem.dicomJson, notAllowedAttr); - set(this.requestWorkItem.dicomJson, originalValueOfNotAllowedAttr); - } - } - - /** - * - * @param {WorkItemModel} workItem - * @returns - */ - async triggerUpdateWorkItemEvent(workItem) { - let updateWorkItemDicomJson = new DicomJsonModel(workItem); - let hitSubscriptions = await this.getHitSubscriptions(updateWorkItemDicomJson); - if (hitSubscriptions.length === 0) { - return workItem; - } - let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); - - //Each time the SCP changes the Input Readiness State (0040,4041) Attribute for a UPS instance, the SCP shall send a UPS State Report Event to subscribed SCUs. - let modifiedInputReadLineState = this.requestWorkItem.getString(`${dictionary.keyword.InputReadinessState}`); - let originalInputReadLineState = this.workItem.getString(`${dictionary.keyword.InputReadinessState}`); - if (modifiedInputReadLineState && modifiedInputReadLineState !== originalInputReadLineState) { - this.addUpsEvent( - UPS_EVENT_TYPE.StateReport, - this.workItem.dicomJson.upsInstanceUID, - this.stateReportOf(workItem.toDicomJsonModel()), - hitSubscriptionAeTitleArray - ); - } - - this.addProgressInfoUpdatedEvent(workItem.toDicomJsonModel(), hitSubscriptionAeTitleArray); - this.addAssignedEvents(workItem.toDicomJsonModel(), hitSubscriptionAeTitleArray); - - this.triggerUpsEvents(); - } -} - -module.exports.UpdateWorkItemService = SqlUpdateWorkItemService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js b/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js deleted file mode 100644 index 22a6ec05..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js +++ /dev/null @@ -1,88 +0,0 @@ -const { - BulkDataService, - StudyBulkDataFactory, - SeriesBulkDataFactory, - InstanceBulkDataFactory, - SpecificBulkDataFactory -} = require("@root/api/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata"); -const { DicomBulkDataModel } = require("@models/sql/models/dicomBulkData.model"); -const { Op } = require("sequelize"); - -StudyBulkDataFactory.prototype.getBulkData = async function () { - let { - studyUID - } = this.uids; - - let studyBulkDataArray = await DicomBulkDataModel.findAll({ - where: { - studyUID - } - }); - - return studyBulkDataArray; -}; - -SeriesBulkDataFactory.prototype.getBulkData = async function () { - let { - studyUID, - seriesUID - } = this.uids; - - let seriesBulkDataArray = await DicomBulkDataModel.findAll({ - where: { - studyUID, - seriesUID - } - }); - - return seriesBulkDataArray; -}; - -InstanceBulkDataFactory.prototype.getBulkData = async function () { - let { - studyUID, - seriesUID, - instanceUID - } = this.uids; - - let instanceBulkDataArray = await DicomBulkDataModel.findAll({ - where: { - studyUID, - seriesUID, - instanceUID - } - }); - - return instanceBulkDataArray; -}; - -SpecificBulkDataFactory.prototype.getBulkData = async function () { - let { - studyUID, - seriesUID, - instanceUID, - binaryValuePath - } = this.uids; - - /** @type { import("sequelize").FindOptions } */ - let findOption = { - where: { - studyUID, - seriesUID, - instanceUID, - binaryValuePath: { - [Op.like]: `%${binaryValuePath}%` - } - } - }; - - let bulkData = await DicomBulkDataModel.findOne(findOption); - - return bulkData; -}; - -module.exports.BulkDataService = BulkDataService; -module.exports.StudyBulkDataFactory = StudyBulkDataFactory; -module.exports.SeriesBulkDataFactory = SeriesBulkDataFactory; -module.exports.InstanceBulkDataFactory = InstanceBulkDataFactory; -module.exports.SpecificBulkDataFactory = SpecificBulkDataFactory; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js deleted file mode 100644 index 15476362..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ /dev/null @@ -1,74 +0,0 @@ -const { NotFoundInstanceError } = require("../../../../../../error/dicom-instance"); -const { StudyModel } = require("@models/sql/models/study.model"); -const { SeriesModel } = require("@models/sql/models/series.model"); -const { InstanceModel } = require("@models/sql/models/instance.model"); - -class DeleteService { - /** - * - * @param {import("express").Request} req - * @param {import("express").Response} res - * @param { "study" | "series" | "instance" } level - */ - constructor(req, res, level = "study") { - this.request = req; - this.response = res; - this.level = level; - } - - async delete() { - let deleteFns = {}; - deleteFns["study"] = async () => this.deleteStudy(); - deleteFns["series"] = async () => this.deleteSeries(); - deleteFns["instance"] = async () => this.deleteInstance(); - - await deleteFns[this.level](); - } - - async deleteStudy() { - let study = await StudyModel.findOne({ - where: { - x0020000D: this.request.params.studyUID - } - }); - - if (!study) { - throw new NotFoundInstanceError(`Can not found studyUID: ${this.request.params.studyUID} instances' files`); - } - - await study.incrementDeleteStatus(); - } - - async deleteSeries() { - let aSeries = await SeriesModel.findOne({ - where: { - x0020000D: this.request.params.studyUID, - x0020000E: this.request.params.seriesUID - } - }); - - if (!aSeries) { - throw new NotFoundInstanceError(`Can not found studyUID: ${this.request.params.studyUID}, seriesUID: ${this.request.params.seriesUID}' files`); - } - - await aSeries.incrementDeleteStatus(); - } - - async deleteInstance() { - let instance = await InstanceModel.findOne({ - where: { - x0020000D: this.request.params.studyUID, - x0020000E: this.request.params.seriesUID, - x00080018: this.request.params.instanceUID - } - }); - - if (!instance) { - throw new NotFoundInstanceError(`Can not found studyUID: ${this.request.params.studyUID}, seriesUID: ${this.request.params.seriesUID}, instanceUID: ${this.request.params.instanceUID} instances' files`); - } - - await instance.incrementDeleteStatus(); - } -} - -module.exports.DeleteService = DeleteService; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js deleted file mode 100644 index 4efe492f..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ /dev/null @@ -1,46 +0,0 @@ -const { InstanceModel } = require("@models/sql/models/instance.model"); -const { StudyModel } = require("@models/sql/models/study.model"); -const { SeriesModel } = require("@models/sql/models/series.model"); -const { - getAcceptType, - supportInstanceMultipartType, - sendNotSupportedMediaType, - addHostnameOfBulkDataUrl, - multipartContentTypeWriter, - ImageMultipartWriter, - getUidsString, - ImagePathFactory, - StudyImagePathFactory, - SeriesImagePathFactory, - InstanceImagePathFactory -} = require("@root/api/dicom-web/controller/WADO-RS/service/WADO-RS.service"); - - -StudyImagePathFactory.prototype.getImagePaths = async function () { - this.imagePaths = await StudyModel.getPathGroupOfInstances(this.uids); -}; - -SeriesImagePathFactory.prototype.getImagePaths = async function () { - this.imagePaths = await SeriesModel.getPathGroupOfInstances(this.uids); -}; - -InstanceImagePathFactory.prototype.getImagePaths = async function () { - let imagePath = await InstanceModel.getPathOfInstance(this.uids); - - if (imagePath) - this.imagePaths = [imagePath]; - else - this.imagePaths = []; -}; - -module.exports.getAcceptType = getAcceptType; -module.exports.supportInstanceMultipartType = supportInstanceMultipartType; -module.exports.sendNotSupportedMediaType = sendNotSupportedMediaType; -module.exports.addHostnameOfBulkDataUrl = addHostnameOfBulkDataUrl; -module.exports.ImagePathFactory = ImagePathFactory; -module.exports.StudyImagePathFactory = StudyImagePathFactory; -module.exports.SeriesImagePathFactory = SeriesImagePathFactory; -module.exports.InstanceImagePathFactory = InstanceImagePathFactory; -module.exports.multipartContentTypeWriter = multipartContentTypeWriter; -module.exports.ImageMultipartWriter = ImageMultipartWriter; -module.exports.getUidsString = getUidsString; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js deleted file mode 100644 index 6ec59e04..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js +++ /dev/null @@ -1,316 +0,0 @@ -const { - handleImageQuality, - handleViewport, - writeRenderedImages, - RenderedImageMultipartWriter -} = require("@root/api/dicom-web/controller/WADO-RS/service/rendered.service"); -const fs = require("fs"); -const sharp = require("sharp"); -const path = require("path"); -const _ = require("lodash"); -const { MultipartWriter } = require("@root/utils/multipartWriter"); -const notImageSOPClass = require("@models/DICOM/dicomWEB/notImageSOPClass"); -const Magick = require("@models/magick"); - -const errorResponse = require("@root/utils/errorResponse/errorResponseMessage"); -const { logger } = require("@root/utils/logs/log"); - -const { raccoonConfig } = require("@root/config-class"); -const { Op } = require("sequelize"); -const { InstanceModel } = require("@models/sql/models/instance.model.js"); -const { DicomBulkDataModel } = require("@models/sql/models/dicomBulkData.model"); -const { default: Dcm2JpgExecutor } = require("@java-wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); -const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("@java-wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); - -/** - * - * @param {*} param The req.query - * @param {Magick} magick - * @param {string} instanceID - */ -async function handleImageICCProfile(param, magick, instanceID) { - let iccProfileAction = { - "no" : async ()=> {}, - "yes": async ()=> { - let iccProfileBinaryFile = await DicomBulkDataModel.findOne({ - where: { - binaryValuePath: "00480105.Value.0.00282000.InlineBinary", - instanceUID: instanceID - } - }); - if(!iccProfileBinaryFile) throw new Error("The Image dose not have icc profile tag"); - let iccProfileSrc = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename); - let dest = path.join(raccoonConfig.dicomWebConfig.storeRootPath, iccProfileBinaryFile.filename + `.icc`); - if (!fs.existsSync(dest)) fs.copyFileSync(iccProfileSrc, dest); - magick.iccProfile(dest); - }, - "srgb": async ()=> { - magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/sRGB.icc")); - }, - "adobergb": async () => { - magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/adobeRGB.icc")); - }, - "rommrgb": async ()=> { - magick.iccProfile(path.join(process.cwd(), "models/DICOM/dicomWEB/iccprofiles/rommRGB.icc")); - } - }; - try { - if (param.iccprofile) { - await iccProfileAction[param.iccprofile](); - } - } catch(e) { - console.error("set icc profile error:" , e); - throw e; - } -} - -class FramesWriter { - /** - * - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[]} imagePaths - */ - constructor(req, res, imagePaths) { - this.request = req; - this.response = res; - this.imagePaths = imagePaths; - } - - async write() { - let multipartWriter = new MultipartWriter([], this.request, this.response); - for (let imagePathObj of this.imagePaths) { - let instanceFramesObj = await getInstanceFrameObj(imagePathObj); - if (_.isUndefined(instanceFramesObj)) continue; - let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); - dicomNumberOfFrames = parseInt(dicomNumberOfFrames); - await writeRenderedImages(this.request, dicomNumberOfFrames, instanceFramesObj, multipartWriter); - } - multipartWriter.writeFinalBoundary(); - } -} - -class StudyFramesWriter extends FramesWriter { - /** - * - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").ImagePathObj[]} imagePaths - */ - constructor(req, res, imagePaths) { - super(req, res, imagePaths); - } -} - -class SeriesFramesWriter extends FramesWriter { - constructor(req, res, imagePaths) { - super(req, res, imagePaths); - } -} - -class InstanceFramesWriter extends FramesWriter { - constructor(req, res, imagePaths) { - super(req, res, imagePaths); - } - - async write() { - let multipartWriter = new MultipartWriter([], this.request, this.response); - let instanceFramesObj = await getInstanceFrameObj(this.imagePaths[0]); - if (_.isUndefined(instanceFramesObj)) { - return this.response.status(400).json( - errorResponse.getBadRequestErrorMessage(`instance: ${this.request.params.instanceUID} doesn't have pixel data`) - ); - } - let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); - dicomNumberOfFrames = parseInt(dicomNumberOfFrames); - await writeRenderedImages(this.request, dicomNumberOfFrames, instanceFramesObj, multipartWriter); - multipartWriter.writeFinalBoundary(); - } -} - -class InstanceFramesListWriter extends FramesWriter { - constructor(req, res, imagePaths) { - super(req, res, imagePaths); - this.instanceFramesObj = {}; - this.dicomNumberOfFrames = 1; - } - - async write() { - let { frameNumber } = this.request.params; - - this.instanceFramesObj = await getInstanceFrameObj(this.imagePaths[0]); - if (_.isUndefined(this.instanceFramesObj)) { - return this.response.status(400).json( - errorResponse.getBadRequestErrorMessage(`instance: ${this.request.params.instanceUID} doesn't have pixel data`) - ); - } - this.dicomNumberOfFrames = _.get(this.instanceFramesObj, "x00280008", 1); - this.dicomNumberOfFrames = parseInt(this.dicomNumberOfFrames); - - if (this.isInvalidFrameNumber()) return; - - if (frameNumber.length == 1) { - return this.writeSingleFrame(); - } else { - let multipartWriter = new MultipartWriter([], this.request, this.response); - await writeRenderedImages(this.request, frameNumber, this.instanceFramesObj, multipartWriter); - multipartWriter.writeFinalBoundary(); - return true; - } - } - - isInvalidFrameNumber() { - for (let i = 0; i < this.request.params.frameNumber.length; i++) { - let frame = this.request.params.frameNumber[i]; - if (frame > this.dicomNumberOfFrames) { - let badRequestMessage = errorResponse.getBadRequestErrorMessage(`Bad frame number , \ -This instance NumberOfFrames is : ${this.dicomNumberOfFrames} , But request ${JSON.stringify(this.request.params.frameNumber)}`); - this.response.writeHead(badRequestMessage.HttpStatus, { - "Content-Type": "application/dicom+json" - }); - - let badRequestMessageStr = JSON.stringify(badRequestMessage); - - logger.warn(badRequestMessageStr); - - return this.response.end(JSON.stringify(badRequestMessageStr)); - } - } - return false; - } - - async writeSingleFrame() { - let postProcessResult = await postProcessFrameImage(this.request, this.request.params.frameNumber[0], this.instanceFramesObj); - if (postProcessResult.status) { - this.response.writeHead(200, { - "Content-Type": "image/jpeg" - }); - - return postProcessResult.magick.toBuffer(); - } - throw new Error(`Can not process this image, instanceUID: ${this.instanceFramesObj.instanceUID}, frameNumber: ${this.request.frameNumber[0]}`); - } -} - -/** - * SQL 的彈性比較低,此 function 採與 MongoDB 相同呼叫方式,但在欄位設計上較死,otherFields 無用處 - * - * SQL has lower flexibility compared to MongoDB. - * This function adopts the same calling method as MongoDB, but it is more rigid in terms of field design. - * Note that otherFields is not used. - * @param {Object} iParam - * @return { Promise | Promise } - */ -async function getInstanceFrameObj(iParam) { - let { studyUID, seriesUID, instanceUID } = iParam; - try { - /** @type { import("sequelize").FindOptions } */ - let query = { - where: { - x0020000D: studyUID, - x0020000E: seriesUID, - x00080018: instanceUID, - x00080016: { - [Op.notIn]: notImageSOPClass - }, - deleteStatus: 0 - }, - attributes: [ - "instancePath", - "x00020010", - "x0020000D", - "x0020000E", - "x00080018", - "x00280008", - "x00281050", - "x00281051" - ] - }; - - let instance = await InstanceModel.findOne(query); - if (instance) { - let instanceJson = instance.toJSON(); - - _.set(instanceJson, "studyUID", instanceJson.x0020000D); - _.set(instanceJson, "seriesUID", instanceJson.x0020000E); - _.set(instanceJson, "instanceUID", instanceJson.x00080018); - _.set(instanceJson, "instancePath", path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - instanceJson.instancePath - )); - - return instanceJson; - } - - return undefined; - } catch (e) { - throw e; - } -} - -/** - * - * @param {import("http").IncomingMessage} req - * @param {import("../../../../../utils/typeDef/WADO-RS/WADO-RS.def").InstanceFrameObj} instanceFramesObj - * @returns - */ -async function postProcessFrameImage(req, frameNumber, instanceFramesObj) { - try { - - let dicomFilename = instanceFramesObj.instancePath; - let jpegFile = dicomFilename.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`); - - - - let dcm2jpgOptions = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync(); - dcm2jpgOptions.frameNumber = frameNumber; - let getFrameImageStatus = await Dcm2JpgExecutor.convertDcmToJpgFromFilename( - dicomFilename, - jpegFile, - dcm2jpgOptions - ); - - if (getFrameImageStatus.status) { - let imageSharp = sharp(jpegFile); - let magick = new Magick(jpegFile); - handleImageQuality( - req.query, - magick - ); - await handleImageICCProfile( - req.query, - magick, - instanceFramesObj.instanceUID - ); - await handleViewport( - req.query, - imageSharp, - magick - ); - await magick.execCommand(); - return { - status: true, - message: "process successful", - magick: magick - }; - } - return { - status: false, - message: "get frame image failed", - magick: undefined - }; - } catch(e) { - console.error(e); - return { - status: false, - message: e, - magick: undefined - }; - } -} - - -module.exports.postProcessFrameImage = postProcessFrameImage; -module.exports.writeRenderedImages = writeRenderedImages; -module.exports.getInstanceFrameObj = getInstanceFrameObj; -module.exports.RenderedImageMultipartWriter = RenderedImageMultipartWriter; -module.exports.StudyFramesWriter = StudyFramesWriter; -module.exports.SeriesFramesWriter = SeriesFramesWriter; -module.exports.InstanceFramesWriter = InstanceFramesWriter; -module.exports.InstanceFramesListWriter = InstanceFramesListWriter; \ No newline at end of file diff --git a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js deleted file mode 100644 index 10b16063..00000000 --- a/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ /dev/null @@ -1,33 +0,0 @@ -const renderedService = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); -const _ = require("lodash"); -const { - ThumbnailService, - StudyThumbnailFactory, - SeriesThumbnailFactory, - InstanceThumbnailFactory -} = require("@root/api/dicom-web/controller/WADO-RS/service/thumbnail.service"); - -ThumbnailService.prototype.getThumbnailByInstance = async function (instanceFramesObj) { - let dicomNumberOfFrames = _.get(instanceFramesObj, "x00280008", 1); - dicomNumberOfFrames = parseInt(dicomNumberOfFrames); - let medianFrame = 1; - if (dicomNumberOfFrames > 1) medianFrame = dicomNumberOfFrames >> 1; - if (this.request.params.frameNumber) { - medianFrame = this.request.params.frameNumber[0]; - } - - let postProcessResult = await renderedService.postProcessFrameImage(this.request, medianFrame, instanceFramesObj); - if (postProcessResult.status) { - this.response.writeHead(200, { - "Content-Type": "image/jpeg" - }); - this.apiLogger.logger.info(`Get instance's thumbnail successfully, instance UID: ${instanceFramesObj.instanceUID}`); - return postProcessResult.magick.toBuffer(); - } - return undefined; -}; - -module.exports.ThumbnailService = ThumbnailService; -module.exports.StudyThumbnailFactory = StudyThumbnailFactory; -module.exports.SeriesThumbnailFactory = SeriesThumbnailFactory; -module.exports.InstanceThumbnailFactory = InstanceThumbnailFactory; \ No newline at end of file diff --git a/dimse-sql/index.js b/dimse-sql/index.js deleted file mode 100644 index 49196011..00000000 --- a/dimse-sql/index.js +++ /dev/null @@ -1,53 +0,0 @@ -const { java } = require("@models/DICOM/dcm4che/java-instance"); - -const { BasicCEchoSCP } = require("@dcm4che/net/service/BasicCEchoSCP"); -const { DicomServiceRegistry } = require("@dcm4che/net/service/DicomServiceRegistry"); -const { JsCStoreScp } = require("../dimse/c-store"); -const { JsCFindScp } = require("../dimse/c-find"); -const { JsCMoveScp } = require("../dimse/c-move"); -const { JsCGetScp } = require("../dimse/c-get"); -const { DcmQrScp } = require("@root/dimse"); - -class SqlDcmQrScp extends DcmQrScp { - - constructor() { - super(); - } - - async createDicomServiceRegistry() { - let dicomServiceRegistry = new DicomServiceRegistry(); - - await dicomServiceRegistry.addDicomService(new BasicCEchoSCP()); - - // await dicomServiceRegistry.addDicomService(new JsStgCmtScp(this).get()); - - // #region C-STORE - let jsCStoreScp = new JsCStoreScp(); - await dicomServiceRegistry.addDicomService(jsCStoreScp.get()); - // #endregion - - // #region C-FIND - await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientRootLevel()); - await dicomServiceRegistry.addDicomService(new JsCFindScp().getStudyRootLevel()); - await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientStudyOnlyLevel()); - // #endregion - - // #region C-MOVE - await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientRootLevel()); - await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getStudyRootLevel()); - await dicomServiceRegistry.addDicomService(new JsCMoveScp(this).getPatientStudyOnlyLevel()); - // #endregion - - // #region C-GET - await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientRootLevel()); - await dicomServiceRegistry.addDicomService(new JsCGetScp().getStudyRootLevel()); - await dicomServiceRegistry.addDicomService(new JsCGetScp().getPatientStudyOnlyLevel()); - await dicomServiceRegistry.addDicomService(new JsCGetScp().getCompositeLevel()); - // #endregion - - return dicomServiceRegistry; - } - -} - -module.exports.DcmQrScp = SqlDcmQrScp; \ No newline at end of file diff --git a/dimse-sql/instanceQueryTask.js b/dimse-sql/instanceQueryTask.js deleted file mode 100644 index ec1cf106..00000000 --- a/dimse-sql/instanceQueryTask.js +++ /dev/null @@ -1,90 +0,0 @@ -const _ = require("lodash"); - -const { JsSeriesQueryTask } = require("./seriesQueryTask"); -const { InstanceQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/InstanceQueryTask"); -const { Attributes } = require("@dcm4che/data/Attributes"); -const { InstanceModel } = require("@models/sql/models/instance.model"); -const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); -const { InstanceQueryTaskInjectProxy, InstanceMatchIteratorProxy } = require("@root/dimse/instanceQueryTask"); -const { QueryTaskUtils } = require("@root/dimse/utils"); - - -class JsInstanceQueryTask extends JsSeriesQueryTask { - constructor(as, pc, rq, keys) { - super(as, pc, rq, keys); - - this.instanceCursor = null; - this.instance = null; - /** @type { Attributes | null } */ - this.instanceAttr = null; - } - - async get() { - let instanceQueryTask = await InstanceQueryTask.newInstanceAsync( - this.as, - this.pc, - this.rq, - this.keys, - this.getQueryTaskInjectProxy(), - this.getPatientQueryTaskInjectProxy(), - this.getStudyQueryTaskInjectProxy(), - this.getSeriesQueryTaskInjectProxy(), - this.getInstanceQueryTaskInjectProxy() - ); - - await super.get(); - await this.instanceQueryTaskInjectProxy.wrappedFindNextInstance(); - - return instanceQueryTask; - } - - getQueryTaskInjectProxy() { - if (!this.matchIteratorProxy) { - this.matchIteratorProxy = new InstanceMatchIteratorProxy(this); - } - return this.matchIteratorProxy.get(); - } - - getInstanceQueryTaskInjectProxy() { - if (!this.instanceQueryTaskInjectProxy) { - this.instanceQueryTaskInjectProxy = new SqlInstanceQueryTaskInjectProxy(this); - } - - return this.instanceQueryTaskInjectProxy.get(); - } - - async getNextInstanceCursor() { - this.instanceOffset = 0; - - let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.seriesAttr); - let dbQuery = await QueryTaskUtils.getDbQuery(queryAttr, "instance"); - let instanceQueryBuilder = new InstanceQueryBuilder({ - query: { - ...dbQuery - } - }); - let q = instanceQueryBuilder.build(); - this.instanceQuery = { - ...q - }; - } - -} - -class SqlInstanceQueryTaskInjectProxy extends InstanceQueryTaskInjectProxy { - constructor(instanceQueryTask) { - super(instanceQueryTask); - } - - async getInstance() { - this.instanceQueryTask.instance = await InstanceModel.findOne({ - ...this.instanceQueryTask.instanceQuery, - attributes: ["json"], - limit: 1, - offset: this.instanceQueryTask.instanceOffset++ - }); - - this.instanceQueryTask.instanceAttr = this.instanceQueryTask.instance ? await this.instanceQueryTask.instance.getAttributes() : null; - } -} -module.exports.JsInstanceQueryTask = JsInstanceQueryTask; \ No newline at end of file diff --git a/dimse-sql/patientQueryTask.js b/dimse-sql/patientQueryTask.js deleted file mode 100644 index 4c0b2fe6..00000000 --- a/dimse-sql/patientQueryTask.js +++ /dev/null @@ -1,83 +0,0 @@ -const _ = require("lodash"); -const { createPatientQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTaskInject"); -const { DimseQueryBuilder } = require("@dimse/queryBuilder"); -const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); -const { PatientModel } = require("@models/sql/models/patient.model"); -const { JsPatientQueryTask } = require("../dimse/patientQueryTask"); -const { QueryTaskUtils } = require("@root/dimse/utils"); - - -class SqlJsPatientQueryTask extends JsPatientQueryTask { - constructor(as, pc, rq, keys) { - super(as, pc, rq, keys); - - this.offset = 0; - this.query = null; - } - - getPatientQueryTaskInjectProxy() { - if (!this.patientQueryTaskProxy) { - this.patientQueryTaskProxy = new SqlPatientQueryTaskInjectProxy(this); - } - return this.patientQueryTaskProxy.get(); - } - - async initCursor() { - this.offset = 0; - let sqlQuery = await QueryTaskUtils.getDbQuery(this.keys, "patient"); - let patientQueryBuilder = new PatientQueryBuilder({ - query: { - ...sqlQuery - } - }); - let q = patientQueryBuilder.build(); - this.query = { - ...q - }; - } - -} - -class SqlPatientQueryTaskInjectProxy { - constructor(patientQueryTask) { - /** @type {SqlJsPatientQueryTask} */ - this.patientQueryTask = patientQueryTask; - } - - get() { - return createPatientQueryTaskInjectProxy(this.getProxyMethods(), { - keepAsDaemon: true - }); - } - - getProxyMethods() { - return { - wrappedFindNextPatient: this.wrappedFindNextPatient.bind(this), - getPatient: this.getPatient.bind(this), - findNextPatient: this.findNextPatient.bind(this) - }; - } - - async wrappedFindNextPatient() { - await this.findNextPatient(); - } - - async findNextPatient() { - await this.getPatient(); - return !_.isNull(this.patientQueryTask.patientAttr); - } - - async getPatient() { - let patient = await PatientModel.findOne({ - ...this.patientQueryTask.query, - attributes: ["json"], - limit: 1, - offset: this.patientQueryTask.offset++ - }); - - this.patientQueryTask.patient = patient; - this.patientQueryTask.patientAttr = this.patientQueryTask.patient ? await this.patientQueryTask.patient.getAttributes() : null; - } -} - -module.exports.JsPatientQueryTask = SqlJsPatientQueryTask; \ No newline at end of file diff --git a/dimse-sql/queryBuilder.js b/dimse-sql/queryBuilder.js deleted file mode 100644 index abbec7e4..00000000 --- a/dimse-sql/queryBuilder.js +++ /dev/null @@ -1,27 +0,0 @@ -const _ = require("lodash"); - -const { DimseQueryBuilder } = require("@root/dimse/queryBuilder"); -const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); - - -class SqlDimseQueryBuilder extends DimseQueryBuilder { - - /** - * - * @param {Attributes} queryKeys - * @param {"patient" | "study" | "series" | "instance"} level - */ - constructor(queryKeys, level="patient") { - super(queryKeys, level); - } - - async build(query) { - return convertAllQueryToDicomTag( - this.cleanEmptyQuery(query), - false - ); - } -} - -module.exports.SqlDimseQueryBuilder = SqlDimseQueryBuilder; -module.exports.DimseQueryBuilder = SqlDimseQueryBuilder; \ No newline at end of file diff --git a/dimse-sql/seriesQueryTask.js b/dimse-sql/seriesQueryTask.js deleted file mode 100644 index 7cc46e5c..00000000 --- a/dimse-sql/seriesQueryTask.js +++ /dev/null @@ -1,93 +0,0 @@ -const _ = require("lodash"); - -const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); -const { DimseQueryBuilder } = require("@dimse/queryBuilder"); -const { JsStudyQueryTask } = require("./studyQueryTask"); -const { SeriesQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SeriesQueryTask"); -const { Attributes } = require("@dcm4che/data/Attributes"); -const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder"); -const { SeriesModel } = require("@models/sql/models/series.model"); -const { Tag } = require("@dcm4che/data/Tag"); -const { SeriesQueryTaskInjectProxy, SeriesMatchIteratorProxy } = require("@root/dimse/seriesQueryTask"); -const { QueryTaskUtils } = require("@root/dimse/utils"); - - -class JsSeriesQueryTask extends JsStudyQueryTask { - constructor(as, pc, rq, keys) { - super(as, pc, rq, keys); - - this.seriesCursor = null; - this.series = null; - /** @type { Attributes | null } */ - this.seriesAttr = null; - } - - async get() { - let seriesQueryTask = await SeriesQueryTask.newInstanceAsync( - this.as, - this.pc, - this.rq, - this.keys, - this.getQueryTaskInjectProxy(), - this.getPatientQueryTaskInjectProxy(), - this.getStudyQueryTaskInjectProxy(), - this.getSeriesQueryTaskInjectProxy() - ); - - await super.get(); - await this.seriesQueryTaskInjectProxy.wrappedFindNextSeries(); - - return seriesQueryTask; - } - - getQueryTaskInjectProxy() { - if (!this.matchIteratorProxy) { - this.matchIteratorProxy = new SeriesMatchIteratorProxy(this); - } - - return this.matchIteratorProxy.get(); - } - - getSeriesQueryTaskInjectProxy() { - if (!this.seriesQueryTaskInjectProxy) { - this.seriesQueryTaskInjectProxy = new SqlSeriesQueryTaskInjectProxy(this); - } - - return this.seriesQueryTaskInjectProxy.get(); - } - - async getNextSeriesCursor() { - this.seriesOffset = 0; - let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.studyAttr, "series"); - let sqlQuery = await QueryTaskUtils.getDbQuery(queryAttr, "series"); - - let seriesQueryBuilder = new SeriesQueryBuilder({ - query: { - ...sqlQuery - } - }); - let q = seriesQueryBuilder.build(); - this.seriesQuery = { - ...q - }; - } - -} - -class SqlSeriesQueryTaskInjectProxy extends SeriesQueryTaskInjectProxy { - constructor(seriesQueryTask) { - super(seriesQueryTask); - } - async getSeries() { - this.seriesQueryTask.series = await SeriesModel.findOne({ - ...this.seriesQueryTask.seriesQuery, - attributes: ["json"], - limit: 1, - offset: this.seriesQueryTask.seriesOffset++ - }); - - this.seriesQueryTask.seriesAttr = this.seriesQueryTask.series ? await this.seriesQueryTask.series.getAttributes() : null; - } -} - -module.exports.JsSeriesQueryTask = JsSeriesQueryTask; \ No newline at end of file diff --git a/dimse-sql/studyQueryTask.js b/dimse-sql/studyQueryTask.js deleted file mode 100644 index 79854318..00000000 --- a/dimse-sql/studyQueryTask.js +++ /dev/null @@ -1,105 +0,0 @@ -const _ = require("lodash"); - -const { StudyQueryTask } = require("@chinlinlee/dcm777/net/StudyQueryTask"); -const { JsPatientQueryTask } = require("./patientQueryTask"); -const { createStudyQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/StudyQueryTaskInject"); -const { Attributes } = require("@dcm4che/data/Attributes"); -const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder"); -const { StudyModel } = require("@models/sql/models/study.model"); -const { StudyQueryTaskInjectProxy, StudyMatchIteratorProxy } = require("@root/dimse/studyQueryTask"); -const { QueryTaskUtils } = require("@root/dimse/utils"); - -class JsStudyQueryTask extends JsPatientQueryTask { - constructor(as, pc, rq, keys) { - super(as, pc, rq, keys); - - this.study = null; - /** @type { Attributes | null } */ - this.studyAttr = null; - } - - async get() { - let studyQueryTask = await StudyQueryTask.newInstanceAsync( - this.as, - this.pc, - this.rq, - this.keys, - this.getQueryTaskInjectProxy(), - this.getPatientQueryTaskInjectProxy(), - this.getStudyQueryTaskInjectProxy() - ); - - await super.get(); - await this.studyQueryTaskInjectProxy.wrappedFindNextStudy(); - - return studyQueryTask; - } - - getQueryTaskInjectProxy() { - if (!this.queryTaskInjectProxy) { - this.queryTaskInjectProxy = new StudyMatchIteratorProxy(this); - } - - return this.queryTaskInjectProxy.get(); - } - - getStudyQueryTaskInjectProxy() { - if (!this.studyQueryTaskInjectProxy) { - this.studyQueryTaskInjectProxy = new SqlStudyQueryTaskInjectProxy(this); - } - - return this.studyQueryTaskInjectProxy.get(); - } - - async getNextStudyCursor() { - this.studyOffset = 0; - let queryAttr = await QueryTaskUtils.getQueryAttribute(this.keys, this.patientAttr, "study"); - let sqlQuery = await QueryTaskUtils.getDbQuery(queryAttr, "study"); - - let studyQueryBuilder = new StudyQueryBuilder({ - query: { - ...sqlQuery - } - }); - let q = studyQueryBuilder.build(); - this.studyQuery = { - ...q - }; - } - - async auditDicomInstancesAccessed() { - if (!this.study) - return; - - let auditManager = await QueryTaskUtils.getAuditManager(this.as); - let studyUID = _.get(this.study, "x0020000D"); - auditManager.onDicomInstancesAccessed([studyUID]); - } -} - -class SqlStudyQueryTaskInjectProxy extends StudyQueryTaskInjectProxy { - constructor(studyQueryTask) { - super(studyQueryTask); - } - - get() { - return createStudyQueryTaskInjectProxy(this.getProxyMethods(), { - keepAsDaemon: true - }); - } - - async getStudy() { - this.studyQueryTask.study = await StudyModel.findOne({ - ...this.studyQueryTask.studyQuery, - attributes: ["json"], - limit: 1, - offset: this.studyQueryTask.studyOffset++ - }); - - this.studyQueryTask.auditDicomInstancesAccessed(); - this.studyQueryTask.studyAttr = this.studyQueryTask.study ? await this.studyQueryTask.study.getAttributes() : null; - } - -} - -module.exports.JsStudyQueryTask = JsStudyQueryTask; \ No newline at end of file diff --git a/dimse-sql/utils.js b/dimse-sql/utils.js deleted file mode 100644 index 5c302e64..00000000 --- a/dimse-sql/utils.js +++ /dev/null @@ -1,112 +0,0 @@ -const _ = require("lodash"); -const path = require("path"); -const { Attributes } = require("@dcm4che/data/Attributes"); -const { importClass } = require("java-bridge"); -const { raccoonConfig } = require("@root/config-class"); -const { InstanceLocator } = require("@dcm4che/net/service/InstanceLocator"); -const { default: File } = require("@java-wrapper/java/io/File"); -const sequenceInstance = require("@models/sql/instance"); -const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); -const { QueryTaskUtils } = require("@root/dimse/utils"); -/** - * - * @param {number} tag - */ -function intTagToString(tag) { - return tag.toString(16).padStart(8, "0").toUpperCase(); -} - -/** - * - * @param {Attributes} keys - * @returns - */ -async function getInstancesFromKeysAttr(keys) { - const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); - let queryBuilder = new DimseQueryBuilder(keys, "instance"); - let normalQuery = await queryBuilder.toNormalQuery(); - let sqlQuery = await queryBuilder.build(normalQuery); - let instanceQueryBuilder = new InstanceQueryBuilder({ - query: { - ...sqlQuery - } - }); - let q = instanceQueryBuilder.build(); - let instanceQuery = { - ...q - }; - - let instances = await sequenceInstance.model("Instance").findAll({ - ...instanceQuery, - attributes: ["json", "instancePath"] - }); - - const JArrayList = await importClass("java.util.ArrayList"); - let list = await JArrayList.newInstanceAsync(); - - for (let instance of instances) { - let instanceFile = await File.newInstanceAsync( - path.resolve( - path.join( - raccoonConfig.dicomWebConfig.storeRootPath, - instance.instancePath - ) - ) - ); - - let fileUri = await instanceFile.toURI(); - let fileUriString = await fileUri.toString(); - - let instanceLocator = await InstanceLocator.newInstanceAsync( - _.get(instance.json, "00080016.Value.0"), - _.get(instance.json, "00080018.Value.0"), - _.get(instance.json, "00020010.Value.0"), - fileUriString - ); - - await list.add(instanceLocator); - } - - return list; -} - -/** - * - * @param {Attributes} keys - * @returns - */ -async function findOneInstanceFromKeysAttr(keys) { - const { SqlDimseQueryBuilder: DimseQueryBuilder } = require("./queryBuilder"); - let queryBuilder = new DimseQueryBuilder(keys, "instance"); - let normalQuery = await queryBuilder.toNormalQuery(); - let sqlQuery = await queryBuilder.getMongoQuery(normalQuery); - let instanceQueryBuilder = new InstanceQueryBuilder({ - query: { - ...sqlQuery - } - }); - let q = instanceQueryBuilder.build(); - let instanceQuery = { - ...q - }; - - let instance = await sequenceInstance.model("Instance").findOne({ - ...instanceQuery, - attributes: ["json"] - }); - - return instance.json; -} - -QueryTaskUtils.getDbQuery = async function (queryAttr, level = "patient") { - let queryBuilder = await QueryTaskUtils.getQueryBuilder(queryAttr, level); - let normalQuery = await queryBuilder.toNormalQuery(); - let dbQuery = await queryBuilder.build(normalQuery); - - return dbQuery; -}; - -module.exports.intTagToString = intTagToString; -module.exports.getInstancesFromKeysAttr = getInstancesFromKeysAttr; -module.exports.findOneInstanceFromKeysAttr = findOneInstanceFromKeysAttr; -module.exports.QueryTaskUtils = QueryTaskUtils; \ No newline at end of file diff --git a/models/sql/deleteSchedule.js b/models/sql/deleteSchedule.js deleted file mode 100644 index a04f03ff..00000000 --- a/models/sql/deleteSchedule.js +++ /dev/null @@ -1,152 +0,0 @@ -const schedule = require("node-schedule"); -const { StudyModel } = require("./models/study.model"); -const { Op } = require("sequelize"); -const moment = require("moment"); -const { logger } = require("@root/utils/logs/log"); -const { InstanceModel } = require("./models/instance.model"); -const { SeriesModel } = require("./models/series.model"); -const { PatientModel } = require("./models/patient.model"); - -// Delete dicom with delete status >= 2 -schedule.scheduleJob("0 0 */1 * * *", async function () { - deleteExpirePatient().catch((e) => { - logger.error(e); - }); - deleteExpireStudies().catch((e) => { - logger.error(e); - }); - deleteExpireSeries().catch((e) => { - logger.error(e); - }); - deleteExpireInstances().catch((e) => { - logger.error(e); - }); -}); - -async function deleteExpirePatient() { - let deletedPatients = await PatientModel.findAll({ - where: { - deleteStatus: { - [Op.gte]: 2 - } - } - }); - - for (let deletedPatient of deletedPatients) { - let updateAtDate = moment(deletedPatient.getDataValue("updatedAt")); - let now = moment(); - let diff = now.diff(updateAtDate, "days"); - if (diff >= 30) { - let patientID = deletedPatient.getDataValue("x00100020"); - - logger.info("delete expired patient: " + patientID); - await Promise.all([ - InstanceModel.destroy({ - where: { - x00100020: patientID - } - }), - SeriesModel.destroy({ - where: { - x00100020: patientID - } - }), - deletedPatient.destroy() - ]); - - await deletedPatient.deleteStudyFolder(); - } - } -} - -async function deleteExpireStudies() { - let deletedStudies = await StudyModel.findAll({ - where: { - deleteStatus: { - [Op.gte]: 2 - } - } - }); - - for (let deletedStudy of deletedStudies) { - let updateAtDate = moment(deletedStudy.getDataValue("updatedAt")); - let now = moment(); - let diff = now.diff(updateAtDate, "days"); - if (diff >= 30) { - let studyUID = deletedStudy.getDataValue("x0020000D"); - - logger.info("delete expired study: " + studyUID); - await Promise.all([ - InstanceModel.destroy({ - where: { - x0020000D: studyUID - } - }), - SeriesModel.destroy({ - where: { - x0020000D: studyUID - } - }), - deletedStudy.destroy() - ]); - - await deletedStudy.deleteStudyFolder(); - } - } -} - -async function deleteExpireSeries() { - let deletedSeries = await SeriesModel.findAll({ - where: { - deleteStatus: { - [Op.gte]: 2 - } - } - }); - - for (let aDeletedSeries of deletedSeries) { - let updateAtDate = moment(aDeletedSeries.getDataValue("updatedAt")); - let now = moment(); - let diff = now.diff(updateAtDate, "days"); - if (diff >= 30) { - let studyUID = aDeletedSeries.getDataValue("x0020000D"); - let seriesUID = aDeletedSeries.getDataValue("x0020000E"); - - logger.info("delete expired series: " + seriesUID); - await Promise.all([ - InstanceModel.destroy({ - where: { - x0020000D: studyUID, - x0020000E: seriesUID - } - }), - aDeletedSeries.destroy() - ]); - - await aDeletedSeries.deleteSeriesFolder(); - } - } -} - -async function deleteExpireInstances() { - let deletedInstances = await InstanceModel.findAll({ - where: { - deleteStatus: { - [Op.gte]: 2 - } - } - }); - - for (let deletedInstance of deletedInstances) { - let instanceUID = deletedInstance.getDataValue("x00080018"); - - let updateAtDate = moment(deletedInstance.getDataValue("updatedAt")); - let now = moment(); - let diff = now.diff(updateAtDate, "days"); - if (diff >= 30) { - logger.info("delete expired instance: " + instanceUID); - await deletedInstance.destroy(); - await deletedInstance.deleteInstance(); - } - } -} \ No newline at end of file diff --git a/models/sql/dicom-json-model.js b/models/sql/dicom-json-model.js deleted file mode 100644 index 4fd110ea..00000000 --- a/models/sql/dicom-json-model.js +++ /dev/null @@ -1,94 +0,0 @@ -const _ = require("lodash"); -const shortHash = require("shorthash2"); -const fsP = require("fs/promises"); -const path = require("path"); -const mkdirp = require("mkdirp"); - -const { BaseDicomJson, DicomJsonModel, DicomJsonBinaryDataModel } = require("@models/DICOM/dicom-json-model"); -const { PatientPersistentObject } = require("./po/patient.po"); -const { StudyPersistentObject } = require("./po/study.po"); -const { SeriesPersistentObject } = require("./po/series.po"); -const { InstancePersistentObject } = require("./po/instance.po"); -const { StudyModel } = require("./models/study.model"); -const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); - -const { raccoonConfig } = require("@root/config-class"); -const { logger } = require("@root/utils/logs/log"); - -DicomJsonModel.prototype.storeToDb = async function (dicomFileSaveInfo) { - let dbJson = this.getCleanDataBeforeStoringToDb(dicomFileSaveInfo); - - try { - let storedPatient = await this.storePatientCollection(dbJson); - let storedStudy = await this.storeStudyCollection(dbJson, storedPatient); - let storedSeries = await this.storeSeriesCollection(dbJson, storedStudy); - await this.storeInstanceCollection(dbJson, storedSeries); - - await StudyModel.updateModalitiesInStudy(storedStudy); - } catch (e) { - throw e; - } -}; -DicomJsonModel.prototype.storePatientCollection = async function (dicomJson) { - let patientPo = new PatientPersistentObject(dicomJson); - let patient = await patientPo.createPatient(); - return patient; -}; - -DicomJsonModel.prototype.storeStudyCollection = async function(dicomJson, patient) { - let studyPo = new StudyPersistentObject(dicomJson, patient); - let study = await studyPo.createStudy(); - return study; -}; - -DicomJsonModel.prototype.storeSeriesCollection = async function (dicomJson, study) { - let seriesPo = new SeriesPersistentObject(dicomJson, study); - let series = await seriesPo.createSeries(); - return series; -}; - -DicomJsonModel.prototype.storeInstanceCollection = async function(dicomJson, series) { - let instancePo = new InstancePersistentObject(dicomJson, series); - return await instancePo.createInstance(); -}; - -class SqlDicomJsonBinaryDataModel extends DicomJsonBinaryDataModel{ - constructor(dicomJsonModel) { - super(dicomJsonModel); - this.bulkDataModelClass = BulkData; - } -} - -class BulkData { - constructor(uidObj, filename, pathOfBinaryProperty) { - /** @type {import("../../utils/typeDef/dicom").UIDObject} */ - this.uidObj = uidObj; - this.filename = filename; - this.pathOfBinaryProperty = pathOfBinaryProperty; - } - - async storeToDb() { - - let item = { - studyUID: this.uidObj.studyUID, - seriesUID: this.uidObj.seriesUID, - instanceUID: this.uidObj.sopInstanceUID, - filename: this.filename, - binaryValuePath: this.pathOfBinaryProperty - }; - - await DicomBulkDataModel.findOrCreate({ - where: { - instanceUID: this.uidObj.sopInstanceUID, - binaryValuePath: this.pathOfBinaryProperty - }, - defaults: item - }); - - logger.info(`[STOW-RS] [Store bulkdata ${JSON.stringify(item)} successful]`); - } -} - -module.exports.DicomJsonModel = DicomJsonModel; -module.exports.BaseDicomJson = BaseDicomJson; -module.exports.DicomJsonBinaryDataModel = SqlDicomJsonBinaryDataModel; \ No newline at end of file diff --git a/models/sql/generate-erd.js b/models/sql/generate-erd.js deleted file mode 100644 index f747929e..00000000 --- a/models/sql/generate-erd.js +++ /dev/null @@ -1,14 +0,0 @@ -require("module-alias/register"); -const fsP = require("fs/promises"); -const sequelize = require("./instance"); -const sequelizeErd = require("sequelize-erd"); - - -require("./init").then(async()=> { - const svg = await sequelizeErd({ - source: sequelize - }); // sequelizeErd() returns a Promise - await fsP.writeFile("./erd.svg", svg); -}); - - diff --git a/models/sql/init.js b/models/sql/init.js deleted file mode 100644 index 819276df..00000000 --- a/models/sql/init.js +++ /dev/null @@ -1,213 +0,0 @@ -const { PersonNameModel } = require("./models/personName.model"); -const { PatientModel } = require("./models/patient.model"); -const { StudyModel } = require("./models/study.model"); -const { SeriesModel } = require("./models/series.model"); -const { InstanceModel } = require("./models/instance.model"); -const { DicomBulkDataModel } = require("./models/dicomBulkData.model"); -const { raccoonConfig } = require("@root/config-class"); - -const sequelizeInstance = require("./instance"); -const { SeriesRequestAttributesModel } = require("./models/seriesRequestAttributes.model"); -const { DicomCodeModel } = require("./models/dicomCode.model"); -const { DicomContentSqModel } = require("./models/dicomContentSQ.model"); -const { VerifyIngObserverSqModel } = require("./models/verifyingObserverSQ.model"); -const { WorkItemModel } = require("./models/workitems.model"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { UpsSubscriptionModel } = require("./models/upsSubscription.model"); -const { UpsRequestAttributesModel } = require("./models/upsRequestAttributes.model"); -const { MwlItemModel } = require("./models/mwlitems.model"); - -async function initDatabasePostgres() { - const { Client } = require("pg"); - const client = new Client({ - user: raccoonConfig.dbConfig.username, - password: raccoonConfig.dbConfig.password, - host: raccoonConfig.dbConfig.host, - port: raccoonConfig.dbConfig.port, - database: "postgres", - logging: raccoonConfig.dbConfig.logging - }); - - await client.connect(); - - try { - let result = await client.query(`SELECT 'CREATE DATABASE ${raccoonConfig.dbConfig.database}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${raccoonConfig.dbConfig.database}')`); - if (result.rowCount > 0 ) { - await client.query(`CREATE DATABASE ${raccoonConfig.dbConfig.database}`); - } - } catch(e) { - console.error(e); - process.exit(1); - } finally { - await client.end(); - } -} - -async function init() { - require("./deleteSchedule"); - - if (raccoonConfig.dbConfig.dialect === "postgres") { - await initDatabasePostgres(); - } - - try { - await sequelizeInstance.authenticate(); - - PatientModel.belongsTo(PersonNameModel, { - foreignKey: "x00100010" - }); - - StudyModel.belongsTo(PatientModel, { - foreignKey: "x00100020", - targetKey: "x00100020" - }); - - StudyModel.belongsTo(PersonNameModel, { - foreignKey: "x00080090" - }); - - StudyModel.hasMany(SeriesModel, { - foreignKey: "x0020000D", - sourceKey: "x0020000D" - }); - SeriesModel.belongsTo(StudyModel, { - foreignKey: "x0020000D", - targetKey: "x0020000D" - }); - - // Performing Physician Name many to many - SeriesModel.belongsToMany(PersonNameModel, { - through: "PerformingPhysicianName", - as: "performingPhysicianName", - sourceKey: "x0020000E", - foreignKey: "x0020000E" - }); - PersonNameModel.belongsToMany(SeriesModel, { - through: "PerformingPhysicianName" - }); - - // Operator's Name many to many - SeriesModel.belongsToMany(PersonNameModel, { - through: "OperatorsName", - as: "operatorsName", - sourceKey: "x0020000E", - foreignKey: "x0020000E" - }); - PersonNameModel.belongsToMany(SeriesModel, { - through: "OperatorsName" - }); - - SeriesModel.hasOne(SeriesRequestAttributesModel, { - foreignKey: "x0020000E", - targetKey: "x0020000E" - }); - - InstanceModel.belongsTo(SeriesModel, { - foreignKey: "x0020000E", - targetKey: "x0020000E" - }); - - InstanceModel.hasOne(DicomCodeModel, { - foreignKey: "SOPInstanceUID", - sourceKey: "x00080018" - }); - - InstanceModel.hasOne(VerifyIngObserverSqModel, { - foreignKey: "SOPInstanceUID", - sourceKey: "x00080018" - }); - VerifyIngObserverSqModel.hasOne(DicomCodeModel, { - foreignKey: "x0040A088" - }); - VerifyIngObserverSqModel.belongsTo(PersonNameModel, { - foreignKey: "x0040A075" - }); - - InstanceModel.hasOne(DicomContentSqModel, { - foreignKey: "SOPInstanceUID", - sourceKey: "x00080018" - }); - DicomContentSqModel.hasOne(DicomCodeModel, { - as: "ConceptNameCode" - }); - DicomContentSqModel.hasOne(DicomCodeModel, { - as: "ConceptCode" - }); - - WorkItemModel.belongsTo(PatientModel, { - foreignKey: "x00100020", - targetKey: "x00100020" - }); - - WorkItemModel.belongsTo(DicomCodeModel, { - foreignKey: "x00404025", - as: dictionary.tag["00404025"] - }); - WorkItemModel.belongsTo(DicomCodeModel, { - foreignKey: "x00404026", - as: dictionary.tag["00404026"] - }); - WorkItemModel.belongsTo(DicomCodeModel, { - foreignKey: "x00404027", - as: dictionary.tag["00404027"] - }); - WorkItemModel.belongsTo(DicomCodeModel, { - foreignKey: "x00404009", - as: dictionary.tag["00404009"] - }); - WorkItemModel.belongsTo(DicomCodeModel, { - foreignKey: "x00404018", - as: dictionary.tag["00404018"] - }); - WorkItemModel.belongsTo(DicomCodeModel, { - foreignKey: "x00080082", - as: dictionary.tag["00080082"] - }); - - WorkItemModel.belongsTo(PersonNameModel, { - foreignKey: "x00404037", - as: dictionary.tag["00404037"] - }); - - WorkItemModel.hasMany(UpsSubscriptionModel); - WorkItemModel.hasOne(UpsRequestAttributesModel, { - foreignKey: "upsInstanceUID", - sourceKey: "upsInstanceUID" - }); - - MwlItemModel.belongsTo(PatientModel, { - targetKey: "x00100020", - foreignKey: "patient_id" - }); - - MwlItemModel.belongsTo(DicomCodeModel, { - foreignKey: "protocol_code", - as: dictionary.tag["00400008"] - }); - MwlItemModel.belongsTo(DicomCodeModel, { - foreignKey: "institution_department_type_code", - as: dictionary.tag["00081041"] - }); - MwlItemModel.belongsTo(DicomCodeModel, { - foreignKey: "institution_code", - as: dictionary.tag["00080082"] - }); - MwlItemModel.belongsTo(PersonNameModel, { - foreignKey: "physician_name", - as: dictionary.tag["00400006"] - }); - - //TODO: 設計完畢後要將 force 刪除 - await sequelizeInstance.sync({ - force: raccoonConfig.dbConfig.forceSync - }); - } catch (e) { - console.error('Unable to connect to the database:', e); - process.exit(1); - } - -} - -module.exports = (() => init())(); - - diff --git a/models/sql/initializer.js b/models/sql/initializer.js deleted file mode 100644 index ccb86508..00000000 --- a/models/sql/initializer.js +++ /dev/null @@ -1 +0,0 @@ -require("./init").then(()=> console.log("Sequelize initialized")); \ No newline at end of file diff --git a/models/sql/instance.js b/models/sql/instance.js deleted file mode 100644 index e71e1d32..00000000 --- a/models/sql/instance.js +++ /dev/null @@ -1,9 +0,0 @@ -const { raccoonConfig } = require("@root/config-class"); -const { Sequelize } = require("sequelize"); - -const sequelize = new Sequelize(raccoonConfig.dbConfig); - -/** - * @type {Sequelize} - */ -module.exports = sequelize; \ No newline at end of file diff --git a/models/sql/models/baseDicom.model.js b/models/sql/models/baseDicom.model.js deleted file mode 100644 index 19243f84..00000000 --- a/models/sql/models/baseDicom.model.js +++ /dev/null @@ -1,27 +0,0 @@ -const { raccoonConfig } = require("@root/config-class"); -const { Model } = require("sequelize"); - -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} -class BaseDicomModel extends Model { - async getAttributes() { - let obj = this.toJSON(); - let jsonStr = JSON.stringify(obj.json); - return await Common.getAttributesFromJsonString(jsonStr); - } - - async incrementDeleteStatus() { - let deleteStatus = this.getDataValue("deleteStatus"); - this.setDataValue("deleteStatus", deleteStatus + 1); - await this.save(); - } - - toDicomJson() { - return this.json; - } -}; - -module.exports.BaseDicomModel = BaseDicomModel; \ No newline at end of file diff --git a/models/sql/models/dicomBulkData.model.js b/models/sql/models/dicomBulkData.model.js deleted file mode 100644 index 1ee82028..00000000 --- a/models/sql/models/dicomBulkData.model.js +++ /dev/null @@ -1,30 +0,0 @@ -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); - -class DicomBulkDataModel extends Model {}; - -DicomBulkDataModel.init({ - studyUID: { - type: vrTypeMapping.UI - }, - seriesUID: { - type: vrTypeMapping.UI - }, - instanceUID: { - type: vrTypeMapping.UI - }, - filename: { - type: DataTypes.TEXT("long") - }, - binaryValuePath: { - type: DataTypes.TEXT("medium") - } -}, { - sequelize: sequelizeInstance, - modelName: "DicomBulkData", - tableName: "DicomBulkData", - freezeTableName: true -}); - -module.exports.DicomBulkDataModel = DicomBulkDataModel; diff --git a/models/sql/models/dicomCode.model.js b/models/sql/models/dicomCode.model.js deleted file mode 100644 index 4efb9c8e..00000000 --- a/models/sql/models/dicomCode.model.js +++ /dev/null @@ -1,38 +0,0 @@ -const { Model } = require("sequelize"); -const sequelizeInstance = require("@root/models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); - -class DicomCodeModel extends Model {}; - -DicomCodeModel.init({ - "x00080100": { - type: vrTypeMapping.SH - }, - "x00080102": { - type: vrTypeMapping.SH - }, - "x00080103": { - type: vrTypeMapping.SH - }, - "x00080104": { - type: vrTypeMapping.LO - } -}, { - sequelize: sequelizeInstance, - modelName: "DicomCode", - tableName: "DicomCode", - freezeTableName: true, - indexes: [ - { - fields: ["x00080100"] - }, - { - fields: ["x00080102"] - }, - { - fields: ["x00080103"] - } - ] -}); - -module.exports.DicomCodeModel = DicomCodeModel; \ No newline at end of file diff --git a/models/sql/models/dicomContentSQ.model.js b/models/sql/models/dicomContentSQ.model.js deleted file mode 100644 index 48b9c7d7..00000000 --- a/models/sql/models/dicomContentSQ.model.js +++ /dev/null @@ -1,42 +0,0 @@ -const { Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); - -class DicomContentSqModel extends Model {} - -DicomContentSqModel.init({ - "x0040A010": { - // Relationship Type - type: vrTypeMapping.CS - }, - "x0040A040": { - // Value Type - type: vrTypeMapping.CS - }, - "x0040A160": { - // Text Value - type: vrTypeMapping.UT, - validate: { - requiredIfValueType(value) { - if (!value && this.x0040A040 === "TEXT") { - throw new Error("x0040A160 is required if x0040A040 is TEXT"); - } - } - } - } -}, { - sequelize: sequelizeInstance, - modelName: "DicomContentSQ", - tableName: "DicomContentSQ", - freezeTableName: true, - indexes: [ - { - fields: ["x0040A010"] - }, - { - fields: ["x0040A160"] - } - ] -}); - -module.exports.DicomContentSqModel = DicomContentSqModel; \ No newline at end of file diff --git a/models/sql/models/dicomToJpegTask.model.js b/models/sql/models/dicomToJpegTask.model.js deleted file mode 100644 index 57c3c03a..00000000 --- a/models/sql/models/dicomToJpegTask.model.js +++ /dev/null @@ -1,61 +0,0 @@ -const _ = require("lodash"); -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); - -class DicomToJpegTaskModel extends Model {}; - -DicomToJpegTaskModel.init({ - studyUID: { - type: vrTypeMapping.UI - }, - seriesUID: { - type: vrTypeMapping.UI - }, - instanceUID: { - type: vrTypeMapping.UI - }, - message: { - type: DataTypes.TEXT("long") - }, - status: { - type: DataTypes.BOOLEAN - }, - taskTime: { - type: DataTypes.DATE - }, - finishedTime: { - type: DataTypes.DATE - }, - fileSize: { - type: DataTypes.TEXT("medium") - } -}, { - sequelize: sequelizeInstance, - modelName: "DicomToJpegTask", - tableName: "DicomToJpegTask", - freezeTableName: true -}); - -DicomToJpegTaskModel.insertOrUpdate = async (item) => { - try { - let [task, created] = await DicomToJpegTaskModel.findOrCreate({ - where: { - studyUID: item.studyUID, - seriesUID: item.seriesUID, - instanceUID: item.instanceUID - }, - defaults: item - }); - - // update - if (!created) { - _.assign(task, item); - await task.save(); - } - } catch(e) { - throw e; - } -}; - -module.exports.DicomToJpegTaskModel = DicomToJpegTaskModel; diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js deleted file mode 100644 index 11711d46..00000000 --- a/models/sql/models/instance.model.js +++ /dev/null @@ -1,240 +0,0 @@ -const fsP = require("fs/promises"); -const path = require("path"); -const { Sequelize, DataTypes, Model, Op } = require("sequelize"); -const _ = require("lodash"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const { InstanceQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { getStoreDicomFullPath } = require("@models/mongodb/service"); -const { logger } = require("@root/utils/logs/log"); -const { raccoonConfig } = require("@root/config-class"); -const { BaseDicomModel } = require("./baseDicom.model"); - -class InstanceModel extends BaseDicomModel { - - async deleteInstance() { - let instancePath = this.getDataValue("instancePath"); - logger.warn("Permanently delete instance: " + instancePath); - await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, instancePath), { - force: true, - recursive: true - }); - } - - /** - * - * @param {string} studyUID - */ - static async getAuditInstancesInfoFromStudyUID(studyUID) { - let instanceInfos = { - sopClassUIDs: [], - accessionNumbers: [], - patientID: "", - patientName: "" - }; - if (!studyUID) return instanceInfos; - - let instances = await sequelizeInstance.model("Instance").findAll({ - where: { - x0020000D: studyUID - } - }); - - for (let instance of instances) { - let sopClassUID = instance.x00080016; - let accessionNumber = instance.x00080050; - let patientID = instance.x00100020; - let patientName = _.get(instance.json, "00100010.Value.0.Alphabetic"); - sopClassUID ? instanceInfos.sopClassUIDs.push(sopClassUID) : null; - accessionNumber ? instanceInfos.accessionNumbers.push(accessionNumber) : null; - patientID ? instanceInfos.patientID = patientID : null; - patientName ? instanceInfos.patientName = patientName : null; - } - - instanceInfos.sopClassUIDs = _.uniq(instanceInfos.sopClassUIDs); - instanceInfos.accessionNumbers = _.uniq(instanceInfos.accessionNumbers); - - return instanceInfos; - } -}; - -InstanceModel.init({ - "instancePath": { - type: DataTypes.TEXT("long") - }, - "x00020010": { - // Transfer Syntax UID - type: vrTypeMapping.UI - }, - "x0020000D": { - type: vrTypeMapping.UI, - allowNull: false - }, - "x0020000E": { - type: vrTypeMapping.UI, - allowNull: false - }, - "x00080018": { - type: vrTypeMapping.UI, - allowNull: false, - unique: true, - primaryKey: true - }, - "x00080016": { - type: vrTypeMapping.UI - }, - "x00080022": { - type: vrTypeMapping.DA - }, - "x00080023": { - type: vrTypeMapping.DA - }, - "x0008002A": { - type: vrTypeMapping.DT - }, - "x00080033": { - type: vrTypeMapping.TM - }, - "x00200013": { - type: vrTypeMapping.IS - }, - "x00280008": { - // Number of Frames - type: vrTypeMapping.IS - }, - "x00281050": { - type: vrTypeMapping.DS, - get() { - const rawValue = this.getDataValue("x00281050"); - return rawValue ? rawValue.split("\\") : undefined; - } - }, - "x00281051": { - type: vrTypeMapping.DS, - get() { - const rawValue = this.getDataValue("x00281050"); - return rawValue ? rawValue.split("\\") : undefined; - } - }, - "x0040A491": { - type: vrTypeMapping.CS - }, - "x0040A493": { - type: vrTypeMapping.CS - }, - "json": { - type: vrTypeMapping.JSON - }, - "deleteStatus": { - type: DataTypes.INTEGER, - defaultValue: 0 - } -}, { - sequelize: sequelizeInstance, - modelName: "Instance", - tableName: "Instance", - freezeTableName: true -}); - -InstanceModel.getDicomJson = async function (queryOptions) { - let queryBuilder = new InstanceQueryBuilder(queryOptions); - let q = queryBuilder.build(); - if (q[Op.and]) { - q[Op.and].push( - { - deleteStatus: 0 - } - ); - } else { - q[Op.and] = [ - { - deleteStatus: 0 - } - ]; - } - let seriesArray = await InstanceModel.findAll({ - ...q, - attributes: ["json", "x0020000D", "x0020000E", "x00080018"], - limit: queryOptions.limit, - offset: queryOptions.skip - }); - - return await Promise.all(seriesArray.map(async series => { - let { json } = series.toJSON(); - // Set Retrieve URL - let studyInstanceUID = _.get(json, "0020000D.Value.0"); - let seriesInstanceUID = _.get(json, "0020000E.Value.0"); - let sopInstanceUID = _.get(json, "00080018.Value.0"); - _.set(json, dictionary.keyword.RetrieveURL, { - vr: dictionary.tagVR[dictionary.keyword.RetrieveURL].vr, - Value: [ - `${queryOptions.retrieveBaseUrl}/${studyInstanceUID}/series/${seriesInstanceUID}/instances/${sopInstanceUID}` - ] - }); - return json; - })); -}; - -InstanceModel.getPathOfInstance = async function (iParam) { - let { studyUID, seriesUID, instanceUID } = iParam; - - try { - let instance = await sequelizeInstance.model("Instance").findOne({ - where: { - x0020000D: studyUID, - x0020000E: seriesUID, - x00080018: instanceUID, - deleteStatus: 0 - } - }); - - if (instance) { - let instanceJson = await instance.toJSON(); - - _.set(instanceJson, "instancePath", getStoreDicomFullPath(instanceJson)); - _.set(instanceJson, "studyUID", instanceJson.x0020000D); - _.set(instanceJson, "seriesUID", instanceJson.x0020000E); - _.set(instanceJson, "instanceUID", instanceJson.x00080018); - - return instanceJson; - } - - return undefined; - } catch (e) { - throw e; - } -}; - -InstanceModel.getInstanceOfMedianIndex = async function (query) { - let instanceCountOfStudy = await InstanceModel.count({ - where: { - x0020000D: query.studyUID, - deleteStatus: 0 - } - }); - - let instance = await InstanceModel.findOne({ - where: { - x0020000D: query.studyUID, - deleteStatus: 0 - }, - attributes: ["x0020000D", "x0020000E", "x00080018", "instancePath"], - offset: instanceCountOfStudy >> 1, - limit: 1, - order: [ - ["x0020000D", "ASC"], - ["x0020000E", "ASC"] - ] - }); - - if (instance) { - _.set(instance, "studyUID", instance.x0020000D); - _.set(instance, "seriesUID", instance.x0020000E); - _.set(instance, "instanceUID", instance.x00080018); - } - - return instance; -}; - -module.exports.InstanceModel = InstanceModel; diff --git a/models/sql/models/mwlitems.model.js b/models/sql/models/mwlitems.model.js deleted file mode 100644 index 8757971d..00000000 --- a/models/sql/models/mwlitems.model.js +++ /dev/null @@ -1,195 +0,0 @@ -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const { raccoonConfig } = require("@root/config-class"); -const { DicomJsonModel } = require("../dicom-json-model"); -const { MwlQueryBuilder } = require("@root/api-sql/dicom-web/controller/MWL-RS/service/query/mwlQueryBuilder"); - -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -class MwlItemModel extends Model { - async getAttributes() { - let obj = this.toJSON(); - let jsonStr = JSON.stringify(obj.json); - return await Common.getAttributesFromJsonString(jsonStr); - } - - toDicomJsonModel() { - return new DicomJsonModel(this.json); - } - static async getDicomJson (queryOptions) { - let queryBuilder = new MwlQueryBuilder(queryOptions); - let q = queryBuilder.build(); - - let mwlItems = await MwlItemModel.findAll({ - ...q, - attributes: ["json"], - limit: queryOptions.limit, - offset: queryOptions.skip - }); - - return await Promise.all(mwlItems.map(async item => { - return item.json; - })); - } - - static async getCount(query) { - let queryBuilder = new MwlQueryBuilder({query}); - let q = queryBuilder.build(); - return await this.count({ - ...q - }); - } - - static async deleteByStudyInstanceUIDAndSpsID(studyUID, spsID) { - let deletedCount = await MwlItemModel.destroy({ - where: { - study_instance_uid: studyUID, - sps_id: spsID - } - }); - return { deletedCount }; - } -}; - -/** @type { import("sequelize").ModelAttributes } */ -const MwlItemSchema = { - // 0020000D - study_instance_uid: { - type: vrTypeMapping.UI, - allowNull: false, - unique: true - }, - // 00100010 - patient_id: { - type: vrTypeMapping.LO, - allowNull: false - }, - // 00800050 - accession_number: { - type: vrTypeMapping.SH - }, - // 00800051.00400031 - accno_local_id: { - type: vrTypeMapping.UT - }, - // 00800051.00400032 - accno_universal_id: { - type: vrTypeMapping.UT - }, - // 00800051.00400033 - accno_universal_id_type: { - type: vrTypeMapping.CS - }, - // 00401001 - requested_procedure_id: { - type: vrTypeMapping.SH - }, - // 00380010 - admission_id: { - type: vrTypeMapping.LO - }, - // 00380014.00400031 - issuer_admission_local_id: { - type: vrTypeMapping.UT - }, - // 00380014.00400032 - issuer_admission_universal_id: { - type: vrTypeMapping.UT - }, - // 00380014.00400033 - issuer_admission_universal_id_type: { - type: vrTypeMapping.CS - }, - // TODO Scheduled Procedure Step Sequence - // 0040,0100.00400001 - station_ae_title: { - type: vrTypeMapping.AE - }, - // 0040,0100.00400010 - station_name: { - type: vrTypeMapping.SH - }, - // 0040,0100.00400002 - start_date: { - type: vrTypeMapping.DA - }, - // 0040,0100.00400004 - end_date: { - type: vrTypeMapping.DA - }, - // 0040,0100.00400003 - start_time: { - type: vrTypeMapping.DT - }, - // 0040,0100.00400005 - end_time: { - type: vrTypeMapping.DT - }, - // 0040,0100.00400006 - physician_name: { - //* must reference to PersonName model - type: vrTypeMapping.PN - }, - // 0040,0100.00400011 - procedure_step_location: { - type: vrTypeMapping.SH - }, - // 0040,0100.00400007 - description: { - type: vrTypeMapping.LO - }, - // 0040,0100.00400008 - protocol_code: { - // reference to dicom code model - type: DataTypes.INTEGER - }, - // 00080080 - institution_name: { - type: vrTypeMapping.LO - }, - // 00081040 - institution_department_name: { - type: vrTypeMapping.LO - }, - // Reference to Dicom Code Model - // 00081041 - institution_department_type_code: { - type: DataTypes.INTEGER - }, - // Reference to Dicom Code Model - // 00080082 - institution_code: { - type: DataTypes.INTEGER - }, - // 00400100.00400009 - sps_id: { - type: vrTypeMapping.SH - }, - // 00400100.00400020 - sps_status: { - type: vrTypeMapping.CS - }, - // 00400100.00080060 - modality: { - type: vrTypeMapping.CS - }, - json: { - type: vrTypeMapping.JSON - } -}; - - -MwlItemModel.init(MwlItemSchema, { - sequelize: sequelizeInstance, - modelName: "mwl_item", - tableName: "mwl_item", - freezeTableName: true -}); - -module.exports.MwlItemModel = MwlItemModel; -module.exports.MwlItemSchema = MwlItemSchema; diff --git a/models/sql/models/patient.model.js b/models/sql/models/patient.model.js deleted file mode 100644 index a6d54bc2..00000000 --- a/models/sql/models/patient.model.js +++ /dev/null @@ -1,83 +0,0 @@ -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const { PatientQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/patientQueryBuilder"); -const { raccoonConfig } = require("@root/config-class"); -const { BaseDicomModel } = require("./baseDicom.model"); - -class PatientModel extends BaseDicomModel { - static async updateOrCreatePatient(patient) { - /** @type {PatientModel | null} */ - const { PatientPersistentObject } = require("../po/patient.po"); - let patientPersistent = new PatientPersistentObject(patient); - let bringPatient = await patientPersistent.createPatient(); - - return bringPatient; - } -}; - -PatientModel.init({ - "x00100010": { - type: DataTypes.INTEGER - }, - "x00100020": { - type: vrTypeMapping.LO, - allowNull: false, - unique: true - }, - "x00100021": { - type: vrTypeMapping.LO - }, - "x00100030": { - type: vrTypeMapping.DA - }, - "x00100032": { - type: vrTypeMapping.TM - }, - "x00100040": { - type: vrTypeMapping.CS - }, - "x00102160": { - type: vrTypeMapping.SH - }, - "x00104000": { - type: vrTypeMapping.LT - }, - "x00880130": { - type: vrTypeMapping.SH - }, - "x00880140": { - type: vrTypeMapping.UI - }, - "json": { - type: vrTypeMapping.JSON - }, - "deleteStatus": { - type: DataTypes.INTEGER - } -}, { - sequelize: sequelizeInstance, - modelName: "Patient", - tableName: "Patient", - freezeTableName: true -}); - -PatientModel.getDicomJson = async function (queryOptions) { - let queryBuilder = new PatientQueryBuilder(queryOptions); - let q = queryBuilder.build(); - let studies = await PatientModel.findAll({ - ...q, - attributes: ["json"], - limit: queryOptions.limit, - offset: queryOptions.skip - }); - - - return await Promise.all(studies.map(async study => { - let { json } = study.toJSON(); - - return json; - })); -}; - -module.exports.PatientModel = PatientModel; diff --git a/models/sql/models/personName.model.js b/models/sql/models/personName.model.js deleted file mode 100644 index 9e0be69b..00000000 --- a/models/sql/models/personName.model.js +++ /dev/null @@ -1,94 +0,0 @@ -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { get } = require("lodash"); - -class PersonNameModel extends Model { - - /** - * - * @param {any} nameObj - * @returns - */ - static async createPersonName(nameObj) { - if (!PersonNameModel.isEmpty(nameObj)) { - return await PersonNameModel.create({ - alphabetic: get(nameObj, "Alphabetic", undefined), - ideographic: get(nameObj, "Ideographic", undefined), - phonetic: get(nameObj, "Phonetic", undefined) - }); - } - return undefined; - } - - /** - * - * @param {any} nameObj - * @param {string} id - * @returns - */ - static async updatePersonNameById(nameObj, id) { - if (!PersonNameModel.isEmpty(nameObj)) { - return await PersonNameModel.update({ - alphabetic: get(nameObj, "Alphabetic", undefined), - ideographic: get(nameObj, "Ideographic", undefined), - phonetic: get(nameObj, "Phonetic", undefined) - }, { - where: { - id: id - } - }); - } - return undefined; - } - - /** - * - * @param {any} item - * @param {string} field - * @returns - */ - static async createPersonNames(item, field) { - let personNames = []; - if (item[field]) { - for (let personName of item[field]) { - let personNameSequelize = await PersonNameModel.create({ - alphabetic: get(personName, "Alphabetic", undefined), - ideographic: get(personName, "Ideographic", undefined), - phonetic: get(personName, "Phonetic", undefined) - }); - personNames.push(personNameSequelize); - } - } - return personNames; - } - - static isEmpty(json) { - return !json && !(json?.Alphabetic || json?.Ideographic || json?.Phonetic); - } -} -PersonNameModel.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - alphabetic: { - type: DataTypes.STRING - }, - ideographic: { - type: DataTypes.STRING, - allowNull: true - }, - phonetic: { - type: DataTypes.STRING, - allowNull: true - } -}, { - sequelize: sequelizeInstance, - modelName: "PersonName", - tableName: "PersonName", - freezeTableName: true -}); - - -module.exports.PersonNameModel = PersonNameModel; diff --git a/models/sql/models/series.model.js b/models/sql/models/series.model.js deleted file mode 100644 index 406d0424..00000000 --- a/models/sql/models/series.model.js +++ /dev/null @@ -1,167 +0,0 @@ -const fsP = require("fs/promises"); -const path = require("path"); -const { Sequelize, DataTypes, Model, Op } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const { SeriesQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder"); -const _ = require("lodash"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); -const { logger } = require("@root/utils/logs/log"); -const { raccoonConfig } = require("@root/config-class"); -const { BaseDicomModel } = require("./baseDicom.model"); - -class SeriesModel extends BaseDicomModel { - getSeriesPath() { - return this.getDataValue("seriesPath"); - } - - async deleteSeriesFolder() { - let seriesPath = this.getDataValue("seriesPath"); - logger.warn("Permanently delete series folder: " + seriesPath); - await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, seriesPath), { - force: true, - recursive: true - }); - } -}; - -SeriesModel.init({ - "seriesPath": { - type: DataTypes.TEXT("long") - }, - "x0020000D": { - type: vrTypeMapping.UI, - allowNull: false - }, - "x0020000E": { - type: vrTypeMapping.UI, - allowNull: false, - unique: true, - primaryKey: true - }, - "x00080021": { - type: vrTypeMapping.DA - }, - "x00080060": { - type: vrTypeMapping.CS - }, - "x0008103E": { - type: vrTypeMapping.LO - }, - "x0008103F": { - // Temp field for future use - // vr: SQ - // VM: 1 - type: vrTypeMapping.JSON - }, - "x00081052": { - // Temp field for future use - // vr: SQ - // VM: 1 - type: vrTypeMapping.JSON - }, - "x00081072": { - // Temp field for future use - // vr: SQ - // VM: 1 - type: vrTypeMapping.JSON - }, - "x00081250": { - // Temp field for future use - // vr: SQ - // VM: 1 - type: vrTypeMapping.JSON - }, - "x00200011": { - type: vrTypeMapping.IS - }, - "x00400244": { - type: vrTypeMapping.DA - }, - "x00400245": { - type: vrTypeMapping.TM - }, - "x00080031": { - type: vrTypeMapping.TM - }, - "json": { - type: vrTypeMapping.JSON - }, - "deleteStatus": { - type: DataTypes.INTEGER, - defaultValue: 0 - } -}, { - sequelize: sequelizeInstance, - modelName: "Series", - tableName: "Series", - freezeTableName: true -}); - -SeriesModel.getDicomJson = async function(queryOptions) { - let queryBuilder = new SeriesQueryBuilder(queryOptions); - let q = queryBuilder.build(); - if (q[Op.and]) { - q[Op.and].push( - { - deleteStatus: 0 - } - ); - } else { - q[Op.and] = [ - { - deleteStatus: 0 - } - ]; - } - let seriesArray = await SeriesModel.findAll({ - ...q, - attributes: ["json", "x0020000E"], - limit: queryOptions.limit, - offset: queryOptions.skip - }); - - return await Promise.all(seriesArray.map(async series => { - let { json } = series.toJSON(); - // Set Retrieve URL - let studyInstanceUID = _.get(json, "0020000D.Value.0"); - let seriesInstanceUID = _.get(json, "0020000E.Value.0"); - _.set(json, dictionary.keyword.RetrieveURL, { - vr: dictionary.tagVR[dictionary.keyword.RetrieveURL].vr, - Value: [ - `${queryOptions.retrieveBaseUrl}/${studyInstanceUID}/series/${seriesInstanceUID}` - ] - }); - return json; - })); -}; - -SeriesModel.getPathGroupOfInstances = async function(iParam) { - let { studyUID, seriesUID } = iParam; - - try { - let instances = await sequelizeInstance.model("Instance").findAll({ - where: { - x0020000D: studyUID, - x0020000E: seriesUID, - deleteStatus: 0 - }, - attributes: ["instancePath", "x0020000D", "x0020000E", "x00080018"] - }); - - let fullPathGroup = getStoreDicomFullPathGroup(instances); - - return fullPathGroup.map(v=> { - _.set(v, "studyUID", v.x0020000D); - _.set(v, "seriesUID", v.x0020000E); - _.set(v, "instanceUID", v.x00080018); - return v; - }); - - } catch (e) { - throw e; - } -}; - -module.exports.SeriesModel = SeriesModel; diff --git a/models/sql/models/seriesRequestAttributes.model.js b/models/sql/models/seriesRequestAttributes.model.js deleted file mode 100644 index 1c1c1afc..00000000 --- a/models/sql/models/seriesRequestAttributes.model.js +++ /dev/null @@ -1,41 +0,0 @@ -const { DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const _ = require("lodash"); - -class SeriesRequestAttributesModel extends Model {} - -SeriesRequestAttributesModel.init({ - "x0020000E": { - type: vrTypeMapping.UI, - allowNull: false - }, - "x00080050": { - type: vrTypeMapping.SH - }, - "x00080051_x00400031": { - type: vrTypeMapping.UT - }, - "x00080051_x00400032": { - type: vrTypeMapping.UT - }, - "x00080051_x00400033": { - type: vrTypeMapping.CS - }, - "x00321033": { - type: vrTypeMapping.LO - }, - "x00401001": { - type: vrTypeMapping.SH - }, - "x0020000D": { - type: vrTypeMapping.UI - } -}, { - sequelize: sequelizeInstance, - modelName: "SeriesRequestAttributes", - tableName: "SeriesRequestAttributes", - freezeTableName: true -}); - -module.exports.SeriesRequestAttributesModel = SeriesRequestAttributesModel; \ No newline at end of file diff --git a/models/sql/models/study.model.js b/models/sql/models/study.model.js deleted file mode 100644 index 1478f0dd..00000000 --- a/models/sql/models/study.model.js +++ /dev/null @@ -1,210 +0,0 @@ -const fsP = require("fs/promises"); -const path = require("path"); -const { Sequelize, DataTypes, Model, Op } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const { SeriesModel } = require("./series.model"); -const _ = require("lodash"); -const { StudyQueryBuilder } = require("@root/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder"); -const { InstanceModel } = require("./instance.model"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { getStoreDicomFullPathGroup } = require("@models/mongodb/service"); -const { logger } = require("@root/utils/logs/log"); -const { raccoonConfig } = require("@root/config-class"); -const { BaseDicomModel } = require("./baseDicom.model"); - -class StudyModel extends BaseDicomModel { - async getNumberOfStudyRelatedSeries() { - let count = await SeriesModel.count({ - where: { - x0020000D: _.get(this.json, "0020000D.Value.0") - } - }); - return count; - } - - async getNumberOfStudyRelatedInstances() { - let count = await InstanceModel.count({ - where: { - x0020000D: _.get(this.json, "0020000D.Value.0") - } - }); - return count; - } - - async deleteStudyFolder() { - let studyPath = this.getDataValue("studyPath"); - logger.warn("Permanently delete study folder: " + studyPath); - await fsP.rm(path.join(raccoonConfig.dicomWebConfig.storeRootPath, studyPath), { - force: true, - recursive: true - }); - } -}; - -StudyModel.init({ - "studyPath": { - type: DataTypes.TEXT("long") - }, - "x00100020": { - type: vrTypeMapping.LO, - allowNull: false - }, - "x00080005": { - type: vrTypeMapping.JSON - }, - "x00080020": { - type: vrTypeMapping.DA - }, - "x00080030": { - type: vrTypeMapping.TM - }, - "x00080050": { - type: vrTypeMapping.SH - }, - "x00080056": { - type: vrTypeMapping.CS - }, - "x00080090": { - type: vrTypeMapping.PN - }, - "x00080201": { - type: vrTypeMapping.SH - }, - "x0020000D": { - type: vrTypeMapping.UI, - allowNull: false, - unique: true, - primaryKey: true - }, - "x00200010": { - type: vrTypeMapping.SH - }, - "x00201206": { - type: vrTypeMapping.IS - }, - "x00201208": { - type: vrTypeMapping.IS - }, - "json": { - type: vrTypeMapping.JSON - }, - "deleteStatus": { - type: DataTypes.INTEGER, - defaultValue: 0 - } -}, { - sequelize: sequelizeInstance, - modelName: "Study", - tableName: "Study", - freezeTableName: true -}); - -StudyModel.updateModalitiesInStudy = async function (study) { - let seriesArray = await SeriesModel.findAll({ - where: { - x0020000D: study.x0020000D, - deleteStatus: 0 - }, - attributes: [ - [Sequelize.fn("DISTINCT", Sequelize.col("x00080060")), "modality"] - ] - }); - - let modalities = []; - for (let item of seriesArray) { - if (_.get(item, "dataValues.modality")) - modalities.push(item.dataValues.modality); - } - - study.json = { - ...study.json, - "00080061": { - vr: "CS", - Value: modalities - } - }; - await study.save(); -}; - -StudyModel.getDicomJson = async function (queryOptions) { - let queryBuilder = new StudyQueryBuilder(queryOptions); - let q = queryBuilder.build(); - if (q[Op.and]) { - q[Op.and].push( - { - deleteStatus: 0 - } - ); - } else { - q[Op.and] = [ - { - deleteStatus: 0 - } - ]; - } - let studies = await StudyModel.findAll({ - ...q, - attributes: ["json"], - limit: queryOptions.limit, - offset: queryOptions.skip - }); - - - return await Promise.all(studies.map(async study => { - let numberOfStudyRelatedSeries = await study.getNumberOfStudyRelatedSeries(); - let numberOfStudyRelatedInstances = await study.getNumberOfStudyRelatedInstances(); - let { json } = study.toJSON(); - // Set Retrieve URL - _.set(json, dictionary.keyword.RetrieveURL, { - vr: dictionary.tagVR[dictionary.keyword.RetrieveURL].vr, - Value: [`${queryOptions.retrieveBaseUrl}/${_.get(json, "0020000D.Value.0")}`] - }); - - // Set number of Study related series - _.set(json, dictionary.keyword.NumberOfStudyRelatedSeries, { - vr: dictionary.tagVR[dictionary.keyword.NumberOfStudyRelatedSeries].vr, - Value: [ - numberOfStudyRelatedSeries.toString() - ] - }); - - // Set number of Study related instances - _.set(json, dictionary.keyword.NumberOfStudyRelatedInstances, { - vr: dictionary.tagVR[dictionary.keyword.NumberOfStudyRelatedInstances].vr, - Value: [ - numberOfStudyRelatedInstances.toString() - ] - }); - - return json; - })); -}; - -StudyModel.getPathGroupOfInstances = async function (iParam) { - let { studyUID } = iParam; - - try { - let instances = await sequelizeInstance.model("Instance").findAll({ - where: { - x0020000D: studyUID, - deleteStatus: 0 - }, - attributes: ["instancePath", "x0020000D", "x0020000E", "x00080018"] - }); - - let fullPathGroup = getStoreDicomFullPathGroup(instances); - - return fullPathGroup.map(v => { - _.set(v, "studyUID", v.x0020000D); - _.set(v, "seriesUID", v.x0020000E); - _.set(v, "instanceUID", v.x00080018); - return v; - }); - - } catch (e) { - throw e; - } -}; - -module.exports.StudyModel = StudyModel; diff --git a/models/sql/models/upsGlobalSubscription.model.js b/models/sql/models/upsGlobalSubscription.model.js deleted file mode 100644 index f391f062..00000000 --- a/models/sql/models/upsGlobalSubscription.model.js +++ /dev/null @@ -1,56 +0,0 @@ -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); - -class UpsGlobalSubscriptionModel extends Model { - static async cursor(query) { - return new UpsGlobalSubscriptionModelCursor(query); - } -}; - -UpsGlobalSubscriptionModel.init({ - aeTitle: { - type: DataTypes.TEXT - }, - subscribed: { - type: DataTypes.INTEGER, - defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED - }, - queryKeys: { - type: vrTypeMapping.JSON, - allowNull: true - }, - isDeletionLock: { - type: DataTypes.BOOLEAN, - defaultValue: false - } -}, { - sequelize: sequelizeInstance, - modelName: "UpsGlobalSubscription", - tableName: "UpsGlobalSubscription", - freezeTableName: true -}); - -class UpsGlobalSubscriptionModelCursor { - /** - * - * @param {import("sequelize").FindOptions} query - */ - constructor(query) { - /** @type { import("sequelize").FindOptions } */ - this.query = query; - this.offset = 0; - this.item = undefined; - } - - async next() { - return await UpsGlobalSubscriptionModel.findOne({ - ...this.query, - offset: this.offset++ - }); - } -} - - -module.exports.UpsGlobalSubscriptionModel = UpsGlobalSubscriptionModel; diff --git a/models/sql/models/upsRequestAttributes.model.js b/models/sql/models/upsRequestAttributes.model.js deleted file mode 100644 index da0795cb..00000000 --- a/models/sql/models/upsRequestAttributes.model.js +++ /dev/null @@ -1,41 +0,0 @@ -const { DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const _ = require("lodash"); - -class UpsRequestAttributesModel extends Model {} - -UpsRequestAttributesModel.init({ - "upsInstanceUID": { - type: DataTypes.STRING, - allowNull: false - }, - "x00080050": { - type: vrTypeMapping.SH - }, - "x00080051_x00400031": { - type: vrTypeMapping.UT - }, - "x00080051_x00400032": { - type: vrTypeMapping.UT - }, - "x00080051_x00400033": { - type: vrTypeMapping.CS - }, - "x00321033": { - type: vrTypeMapping.LO - }, - "x00401001": { - type: vrTypeMapping.SH - }, - "x0020000D": { - type: vrTypeMapping.UI - } -}, { - sequelize: sequelizeInstance, - modelName: "UpsRequestAttributesModel", - tableName: "UpsRequestAttributesModel", - freezeTableName: true -}); - -module.exports.UpsRequestAttributesModel = UpsRequestAttributesModel; \ No newline at end of file diff --git a/models/sql/models/upsSubscription.model.js b/models/sql/models/upsSubscription.model.js deleted file mode 100644 index f4a78de2..00000000 --- a/models/sql/models/upsSubscription.model.js +++ /dev/null @@ -1,27 +0,0 @@ -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); - -class UpsSubscriptionModel extends Model { -}; - -UpsSubscriptionModel.init({ - aeTitle: { - type: DataTypes.TEXT - }, - subscribed: { - type: DataTypes.INTEGER, - defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED - }, - isDeletionLock: { - type: DataTypes.BOOLEAN, - defaultValue: false - } -}, { - sequelize: sequelizeInstance, - modelName: "UpsSubscription", - tableName: "UpsSubscription", - freezeTableName: true -}); - -module.exports.UpsSubscriptionModel = UpsSubscriptionModel; diff --git a/models/sql/models/verifyingObserverSQ.model.js b/models/sql/models/verifyingObserverSQ.model.js deleted file mode 100644 index c6e29124..00000000 --- a/models/sql/models/verifyingObserverSQ.model.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * This Tag(Verifying Observer SQ, 0040,A073) only used in SR Document - */ -const { Model, DataTypes } = require("sequelize"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const sequelizeInstance = require("../instance"); - -class VerifyIngObserverSqModel extends Model {} - -VerifyIngObserverSqModel.init({ - "x0040A027": { - // Verifying Organization - type: vrTypeMapping.LO - }, - "x0040A030": { - // Verification DateTime - type: vrTypeMapping.DT - }, - "x0040A075": { - // Verifying Observer Name - type: vrTypeMapping.PN - }, - "x0040A088": { - // Verifying Observer Identification Code Sequence (foreign key) - type: DataTypes.INTEGER - } -}, { - sequelize: sequelizeInstance, - modelName: "VerifyingObserverSQ", - tableName: "VerifyingObserverSQ", - freezeTableName: true -}); - -module.exports.VerifyIngObserverSqModel = VerifyIngObserverSqModel; \ No newline at end of file diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js deleted file mode 100644 index c462b0a8..00000000 --- a/models/sql/models/workitems.model.js +++ /dev/null @@ -1,204 +0,0 @@ -const { Sequelize, DataTypes, Model } = require("sequelize"); -const sequelizeInstance = require("@models/sql/instance"); -const { vrTypeMapping } = require("../vrTypeMapping"); -const { raccoonConfig } = require("@root/config-class"); -const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); -const { UpsQueryBuilder } = require("@root/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder"); -const { DicomJsonModel } = require("../dicom-json-model"); -const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); -const { merge } = require("lodash"); - -let Common; -if (raccoonConfig.dicomDimseConfig.enableDimse) { - require("@models/DICOM/dcm4che/java-instance"); - Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; -} - -class WorkItemModel extends Model { - - - async getAttributes() { - let obj = this.toJSON(); - let jsonStr = JSON.stringify(obj.json); - return await Common.getAttributesFromJsonString(jsonStr); - } - - toDicomJsonModel() { - return new DicomJsonModel(this.json); - } - - /** - * - * @param {DicomJsonModel} changedStateWorkItemDicomJsonModel - */ - async changeWorkItemState(changedStateWorkItemDicomJsonModel) { - let changedWorkItemJson = merge(this.json, changedStateWorkItemDicomJsonModel.dicomJson); - this.transactionUID = changedStateWorkItemDicomJsonModel.getString("00081195"); - this.x00741000 = changedStateWorkItemDicomJsonModel.getString("00741000"); - this.json = { - ...this.json, - ...changedWorkItemJson - }; - // Let sequelize know json is changed - this.changed("json", true); - await this.save(); - } - - static async getDicomJson (queryOptions) { - let queryBuilder = new UpsQueryBuilder(queryOptions); - let q = queryBuilder.build(); - - let upsArray = await WorkItemModel.findAll({ - ...q, - attributes: ["json"], - limit: queryOptions.limit, - offset: queryOptions.skip - }); - - return await Promise.all(upsArray.map(async ups => { - let { json } = ups.toJSON(); - return json; - })); - } - - static async findOneWorkItemDicomJsonModel(upsInstanceUID) { - let workItemObj = await WorkItemModel.findOne({ - where: { - upsInstanceUID: upsInstanceUID - } - }); - if (workItemObj) { - let {json} = workItemObj.toJSON(); - return new DicomJsonModel(json); - } else { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - } - - static async findOneWorkItemByUpsInstanceUID(upsInstanceUID) { - let workItemObj = await WorkItemModel.findOne({ - where: { - upsInstanceUID: upsInstanceUID - } - }); - if (!workItemObj) { - throw new DicomWebServiceError( - DicomWebStatusCodes.UPSDoesNotExist, - "The UPS instance not exist", - 404 - ); - } - return workItemObj; - } -}; - -/** @type { import("sequelize").ModelAttributes } */ -const WorkItemSchema = { - upsInstanceUID: { - type: DataTypes.STRING, - allowNull: false, - unique: true - }, - patientID: { - type: DataTypes.STRING, - allowNull: false - }, - transactionUID: { - type: DataTypes.STRING - }, - subscribed: { - type: DataTypes.INTEGER, - defaultValue: SUBSCRIPTION_STATE.NOT_SUBSCRIBED - }, - //#region patient level - "x00100020": { - type: vrTypeMapping.LO, - allowNull: false - }, - //#endregion - "x00741200": { - type: vrTypeMapping.CS - }, - "x00404010": { - type: vrTypeMapping.DT - }, - "x00741204": { - type: vrTypeMapping.LO - }, - "x00741202": { - type: vrTypeMapping.LO - }, - "x00404025": { - // DICOM Code - type: DataTypes.INTEGER - }, - "x00404026": { - // DICOM Code - type: DataTypes.INTEGER - }, - "x00404027": { - // DICOM Code - type: DataTypes.INTEGER - }, - "x00404034": { - // DICOM Code - type: DataTypes.INTEGER - }, - "x00404005": { - type: vrTypeMapping.DT - }, - "x00404011": { - type: vrTypeMapping.DT - }, - "x00404018": { - // DICOM Code - type: DataTypes.INTEGER - }, - "x00380010": { - type: vrTypeMapping.LO - }, - "x00380014_x00400031": { - type: vrTypeMapping.UT - }, - "x00380014_x00400032": { - type: vrTypeMapping.UT - }, - "x00380014_x00400033": { - type: vrTypeMapping.CS - }, - "x00741000": { - type: vrTypeMapping.CS - }, - "x00080082": { - type: DataTypes.INTEGER - }, - // #region Scheduled Human Performers Sequence - "x00404009": { - // DICOM Code - type: DataTypes.INTEGER - }, - "x00404036": { - type: vrTypeMapping.LO - }, - "x00404037": { - type: DataTypes.INTEGER - }, - // #endregion - "json": { - type: vrTypeMapping.JSON - } -}; - -WorkItemModel.init(WorkItemSchema, { - sequelize: sequelizeInstance, - modelName: "UPSWorkItem", - tableName: "UPSWorkItem", - freezeTableName: true -}); - -module.exports.WorkItemModel = WorkItemModel; -module.exports.WorkItemSchema = WorkItemSchema; diff --git a/models/sql/po/instance.po.js b/models/sql/po/instance.po.js deleted file mode 100644 index 2609e969..00000000 --- a/models/sql/po/instance.po.js +++ /dev/null @@ -1,512 +0,0 @@ -const moment = require("moment"); -const _ = require("lodash"); -const { PersonNameModel } = require("../models/personName.model"); -const { InstanceModel } = require("../models/instance.model"); - -const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); -const { DicomCodeModel } = require("../models/dicomCode.model"); -const { DicomContentSqModel } = require("../models/dicomContentSQ.model"); -const { VerifyIngObserverSqModel } = require("../models/verifyingObserverSQ.model"); - -const INSTANCE_STORE_TAGS = { - "00020010": true, - "00080016": true, - "00080018": true, - "00080022": true, - "00080023": true, - "0008002A": true, - "00080033": true, - "00200013": true, - "0040A043": true, - "0040A073": true, - "0040A491": true, - "0040A493": true, - "0040A730": true, - "00080008": true, - "0040A032": true, - "00081115": true, - "00280008": true, - "00280010": true, - "00280011": true, - "00280100": true, - "0040A370": true, - "0040A375": true, - "0040A504": true, - "0040A525": true, - "00420010": true, - "00420012": true, - "00700080": true, - "00700081": true, - "00700082": true, - "00700083": true, - "00700084": true, - "00080005": true, - "00081190": true, - "00080054": true, - "00080056": true, - ...tagsNeedStore.Patient, - ...tagsNeedStore.Study, - ...tagsNeedStore.Series -}; - -class InstancePersistentObject { - constructor(dicomJson, series) { - this.json = {}; - Object.keys(INSTANCE_STORE_TAGS).forEach(key => { - let value = _.get(dicomJson, key); - value ? _.set(this.json, key, value) : undefined; - }); - this.series = series; - this.instancePath = _.get(dicomJson, "instancePath", ""); - - this.x00020010 = _.get(dicomJson, "00020010.Value.0", undefined); - this.x0020000D = this.series.x0020000D; - this.x0020000E = this.series.x0020000E; - this.x00080018 = _.get(dicomJson, "00080018.Value.0", undefined); - this.x00080016 = _.get(dicomJson, "00080016.Value.0", undefined); - this.x00080022 = _.get(dicomJson, "00080022.Value.0", undefined); - this.x00080023 = _.get(dicomJson, "00080023.Value.0", undefined); - this.x0008002A = _.get(dicomJson, "0008002A.Value.0", undefined); - this.x00080033 = _.get(dicomJson, "00080033.Value.0", undefined); - this.x00200013 = _.get(dicomJson, "00200013.Value.0", undefined); - this.x00280008 = _.get(dicomJson, "00280008.Value.0", undefined); - this.x00281050 = _.get(dicomJson, "00281050.Value", undefined); - this.x00281051 = _.get(dicomJson, "00281051.Value", undefined); - this.x0040A043 = _.get(dicomJson, "0040A043.Value.0", undefined); - this.x0040A073 = _.get(dicomJson, "0040A073.Value.0", undefined); - this.x0040A491 = _.get(dicomJson, "0040A491.Value.0", undefined); - this.x0040A493 = _.get(dicomJson, "0040A493.Value.0", undefined); - this.x0040A730 = _.get(dicomJson, "0040A730.Value.0", undefined); - } - - async createConceptNameCodeSq(instance) { - if (this.x0040A043) { - let nameCodeSq = { - "x00080100": _.get(this.x0040A043, "00080100.Value.0", undefined), - "x00080102": _.get(this.x0040A043, "00080102.Value.0", undefined), - "x00080103": _.get(this.x0040A043, "00080103.Value.0", undefined), - "x00080104": _.get(this.x0040A043, "00080104.Value.0", undefined) - }; - await instance.createDicomCode(nameCodeSq); - } - } - - /** - * - * @param {InstanceModel} instance - */ - async createOrUpdateConceptNameCode(instance) { - let instanceConceptNameCode = await instance.getDicomCode(); - if (this.x0040A043) { - let nameCodeSq = { - "x00080100": _.get(this.x0040A043, "00080100.Value.0", undefined), - "x00080102": _.get(this.x0040A043, "00080102.Value.0", undefined), - "x00080103": _.get(this.x0040A043, "00080103.Value.0", undefined), - "x00080104": _.get(this.x0040A043, "00080104.Value.0", undefined) - }; - if (!instanceConceptNameCode) { - // Create - await instance.createDicomCode(nameCodeSq); - } else { - // Update - await DicomCodeModel.update(nameCodeSq, { - where: { - SOPInstanceUID: instance.dataValues.x00080018 - } - }); - } - } else { - // Delete when no concept name code - await DicomCodeModel.destroy({ - where: { - SOPInstanceUID: instance.dataValues.x00080018 - } - }); - } - } - - async createContentItem(instance) { - if (this.x0040A730) { - let contentItem = { - "x0040A040": _.get(this.x0040A730, "0040A040.Value.0", undefined), - "x0040A010": _.get(this.x0040A730, "0040A010.Value.0", undefined), - "x0040A160": _.get(this.x0040A730, "0040A160.Value.0", undefined) - }; - let nameCodeSq = { - "x00080100": _.get(this.x0040A730, "0040A043.Value.0.00080100.Value.0", undefined), - "x00080102": _.get(this.x0040A730, "0040A043.Value.0.00080102.Value.0", undefined), - "x00080103": _.get(this.x0040A730, "0040A043.Value.0.00080103.Value.0", undefined), - "x00080104": _.get(this.x0040A730, "0040A043.Value.0.00080104.Value.0", undefined) - }; - let conceptCodeSq = { - "x00080100": _.get(this.x0040A730, "0040A168.Value.0.00080100.Value.0", undefined), - "x00080102": _.get(this.x0040A730, "0040A168.Value.0.00080102.Value.0", undefined), - "x00080103": _.get(this.x0040A730, "0040A168.Value.0.00080103.Value.0", undefined), - "x00080104": _.get(this.x0040A730, "0040A168.Value.0.00080104.Value.0", undefined) - }; - - if (Object.values(contentItem).some(v => v)) { - let createdContentItem = await instance.createDicomContentSQ(contentItem); - - if (Object.values(nameCodeSq).some(v => v)) { - await createdContentItem.createConceptNameCode(nameCodeSq); - } - - if (Object.values(conceptCodeSq).some(v => v)) { - await createdContentItem.createConceptCode(conceptCodeSq); - } - } - - } - } - - async createOrUpdateContentItem(instance) { - let contentItemPo = new ContentItemPersistentObject(this.x0040A730, instance); - // Create or Update - await contentItemPo.createOrUpdateContentItem(); - - } - - async createOrUpdateVerifyingObserverSq(instance) { - let verifyingObserverSqPo = new VerifyingObserverSqPersistentObject(this.x0040A073, this.x0040A493, instance); - await verifyingObserverSqPo.createOrUpdate(); - } - - - async createInstance() { - - let item = { - json: this.json, - x00020010: this.x00020010, - x0020000D: this.x0020000D, - x0020000E: this.x0020000E, - x00080018: this.x00080018, - x00080016: this.x00080016, - x00080022: this.x00080022 ? this.x00080022 : undefined, - x00080023: this.x00080023, - x0008002A: this.x0008002A ? moment(this.x0008002A, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString(): undefined, - x00080033: this.x00080033 ? Number(this.x00080033) : undefined, - x00200013: this.x00200013, - x00280008: this.x00280008, - x00281050: this.x00281050 ? this.x00281050.join("\\"): undefined, - x00281051: this.x00281051 ? this.x00281051.join("\\"): undefined, - x0040A073: this.x0040A073, - x0040A491: this.x0040A491, - x0040A493: this.x0040A493, - x0040A730: this.x0040A730, - instancePath: this.instancePath, - deleteStatus: 0 - }; - - let [instance, created] = await InstanceModel.findOrCreate({ - where: { - x0020000D: this.x0020000D, - x0020000E: this.x0020000E, - x00080018: this.x00080018 - }, - defaults: item - }); - - if (created) { - // do nothing - await this.createContentItem(instance); - } else { - await InstanceModel.update(item, { - where: { - x00080018: instance.dataValues.x00080018 - } - }); - } - await this.createOrUpdateConceptNameCode(instance); - await this.createOrUpdateContentItem(instance); - await this.createOrUpdateVerifyingObserverSq(instance); - - return instance; - } - -} - -class ContentItemPersistentObject { - /** - * - * @param {any} contentItem - * @param {InstanceModel} instance - */ - constructor(contentItem, instance) { - this.contentSq = { - "x0040A040": _.get(contentItem, "0040A040.Value.0", undefined), - "x0040A010": _.get(contentItem, "0040A010.Value.0", undefined), - "x0040A160": _.get(contentItem, "0040A160.Value.0", undefined) - }; - this.nameCodeSq = { - "x00080100": _.get(contentItem, "0040A043.Value.0.00080100.Value.0", undefined), - "x00080102": _.get(contentItem, "0040A043.Value.0.00080102.Value.0", undefined), - "x00080103": _.get(contentItem, "0040A043.Value.0.00080103.Value.0", undefined), - "x00080104": _.get(contentItem, "0040A043.Value.0.00080104.Value.0", undefined) - }; - this.conceptCodeSq = { - "x00080100": _.get(contentItem, "0040A168.Value.0.00080100.Value.0", undefined), - "x00080102": _.get(contentItem, "0040A168.Value.0.00080102.Value.0", undefined), - "x00080103": _.get(contentItem, "0040A168.Value.0.00080103.Value.0", undefined), - "x00080104": _.get(contentItem, "0040A168.Value.0.00080104.Value.0", undefined) - }; - this.instance = instance; - } - - async getExistContentItem() { - return await this.instance.getDicomContentSQ(); - } - - async createOrUpdateContentItem() { - if (!await this.getExistContentItem()) { - // Create - await this.createDicomContentSq(); - await this.createConceptNameCodeInContentItem(); - await this.createConceptCodeInContentItem(); - } else { - // Update - await this.updateConceptNameCodeInContentItem(); - await this.updateConceptCodeInContentItem(); - await this.updateDicomContentSq(); - } - } - - async createDicomContentSq() { - if (Object.values(this.contentSq).some(v => v)) { - await this.instance.createDicomContentSQ(this.contentSq); - } - } - - async updateDicomContentSq() { - if (Object.values(this.contentSq).some(v => v)) { - // Update value - await DicomContentSqModel.update(this.contentSq, { - where: { - SOPInstanceUID: this.instance.dataValues.x00080018 - } - }); - } else { - // Remove item because of given item does not exist - await DicomContentSqModel.destroy({ - where: { - SOPInstanceUID: this.instance.dataValues.x00080018 - } - }); - } - } - - async createConceptNameCodeInContentItem() { - if (Object.values(this.nameCodeSq).some(v => v)) { - if (this.createdContentSq) - await this.createdContentSq.createConceptNameCode(this.nameCodeSq); - } - } - - async updateConceptNameCodeInContentItem() { - let contentItemInInstance = await this.getExistContentItem(); - if (Object.values(this.nameCodeSq).some(v => v)) { - if (contentItemInInstance) { - let nameCode = await contentItemInInstance.getConceptNameCode(); - if (nameCode) { - await DicomCodeModel.update(this.nameCodeSq, { - where: { - ConceptNameCodeId: contentItemInInstance.dataValues.id - } - }); - } else { - await contentItemInInstance.createConceptNameCode(this.nameCodeSq); - } - } - } else { - if (contentItemInInstance) { - await DicomCodeModel.destroy({ - where: { - ConceptNameCodeId: contentItemInInstance.dataValues.id - } - }); - } - } - } - - async createConceptCodeInContentItem() { - if (Object.values(this.conceptCodeSq).some(v => v)) { - if (this.createdContentSq) - await this.createdContentSq.createConceptCode(this.conceptCodeSq); - } - } - - async updateConceptCodeInContentItem() { - let contentItemInInstance = await this.getExistContentItem(); - if (Object.values(this.nameCodeSq).some(v => v)) { - if (contentItemInInstance) { - let conceptCode = await contentItemInInstance.getConceptCode(); - if (conceptCode) { - await DicomCodeModel.update(this.conceptCodeSq, { - where: { - ConceptCodeId: contentItemInInstance.dataValues.id - } - }); - } else { - await contentItemInInstance.createConceptCode(this.nameCodeSq); - } - } - } else { - if (contentItemInInstance) { - await DicomCodeModel.destroy({ - where: { - ConceptCodeId: contentItemInInstance.dataValues.id - } - }); - } - } - } -} - -class VerifyingObserverSqPersistentObject { - constructor(verifyingObserverSq, verificationFlag, instance) { - if (verificationFlag === "VERIFIED" && !verifyingObserverSq) { - throw new Error("Verifying observer is required when Verification Flag (0040,A493) is VERIFIED"); - } - this.verifyingObserverSq = verifyingObserverSq; - this.instance = instance; - } - - async getExistItem() { - return await this.instance.getVerifyingObserverSQ(); - } - - async createOrUpdate() { - let verifyingObserverSq = { - "x0040A027": _.get(this.verifyingObserverSq, "0040A027.Value.0", undefined), - "x0040A030": _.get(this.verifyingObserverSq, "0040A030.Value.0", undefined) - }; - - - verifyingObserverSq.x0040A030 = verifyingObserverSq.x0040A030 ? - moment(verifyingObserverSq.x0040A030, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString() : - undefined; - - if (!await this.getExistItem()) { - // create - let name = await this.createName(); - let personNameId = name ? name.dataValues.id : undefined; - - let identificationCode = await this.createIdentificationCode(); - let identificationCodeId = identificationCode ? identificationCode.dataValues.id : undefined; - - await this.instance.createVerifyingObserverSQ({ - ...verifyingObserverSq, - x0040A088: identificationCodeId, - x0040A075: personNameId - }); - } else { - let name = await this.updateName(); - let personNameId = name ? name.dataValues.id : undefined; - - let identificationCode = await this.updateIdentificationCode(); - let identificationCodeId = identificationCode ? identificationCode.dataValues.id : undefined; - - await VerifyIngObserverSqModel.update({ - ...verifyingObserverSq, - x0040A088: identificationCodeId, - x0040A075: personNameId - }, { - where: { - SOPInstanceUID: this.instance.dataValues.x00080018 - } - }); - } - - } - - /** - * create Verifying Observer Name - */ - async createName() { - let nameItem = _.get(this.verifyingObserverSq, "0040A075.Value.0"); - return await PersonNameModel.createPersonName(nameItem); - } - - async updateName() { - let verifyingObserverSq = await this.getExistItem(); - /** @type { PersonNameModel | undefined } */ - let name = await verifyingObserverSq.getPersonName(); - let nameItem = _.get(this.verifyingObserverSq, "0040A075.Value.0"); - - if (name) { - let updatedName = await PersonNameModel.updatePersonNameById(nameItem, name.getDataValue("id")); - if (!updatedName) { - await VerifyIngObserverSqModel.update({ - x0040A075: null - }, { - where: { - SOPInstanceUID: this.instance.dataValues.x00080018 - } - }); - await PersonNameModel.destroy({ - where: { - id: name.dataValues.id - } - }); - } else { - return name; - } - } else { - return await this.createName(); - } - return undefined; - } - - async createIdentificationCode() { - let codeItem = _.get(this.verifyingObserverSq, "0040A088.Value.0"); - if (codeItem && Object.values(codeItem).some(v => v)) { - return await DicomCodeModel.create({ - "x00080100": _.get(codeItem, "00080100.Value.0", undefined), - "x00080102": _.get(codeItem, "00080102.Value.0", undefined), - "x00080103": _.get(codeItem, "00080103.Value.0", undefined), - "x00080104": _.get(codeItem, "00080104.Value.0", undefined) - }); - } - return undefined; - } - - async updateIdentificationCode() { - let verifyingObserverSq = await this.getExistItem(); - let code = await verifyingObserverSq.getDicomCode(); - let newCodeItem = _.get(this.verifyingObserverSq, "0040A088.Value.0"); - - if (code) { - if (newCodeItem && Object.values(newCodeItem).some(v => v)) { - await DicomCodeModel.update({ - "x00080100": _.get(newCodeItem, "00080100.Value.0", undefined), - "x00080102": _.get(newCodeItem, "00080102.Value.0", undefined), - "x00080103": _.get(newCodeItem, "00080103.Value.0", undefined), - "x00080104": _.get(newCodeItem, "00080104.Value.0", undefined) - }, { - where: { - id: code.dataValues.id - } - }); - return code; - } - - // delete when empty code - await VerifyIngObserverSqModel.update({ - x0040A088: null - }, { - where: { - SOPInstanceUID: this.instance.dataValues.x00080018 - } - }); - await DicomCodeModel.destroy({ - where: { - id: code.dataValues.id - } - }); - } else { - return await this.createIdentificationCode(); - } - } -} - -module.exports.InstancePersistentObject = InstancePersistentObject; \ No newline at end of file diff --git a/models/sql/po/mwlItem.po.js b/models/sql/po/mwlItem.po.js deleted file mode 100644 index c377429a..00000000 --- a/models/sql/po/mwlItem.po.js +++ /dev/null @@ -1,164 +0,0 @@ -const { get, set, cloneDeep, unset } = require("lodash"); -const { PersonNameModel } = require("../models/personName.model"); -const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); -const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { vrValueTransform } = require("./utils"); -const { DicomCodeModel } = require("../models/dicomCode.model"); -const { MwlItemModel } = require("../models/mwlitems.model"); -const { Op } = require("sequelize"); - - -class MwlItemPersistentObject { - constructor(dicomJson, patient) { - - this.json = {}; - this.initJsonProperties(dicomJson); - - this.patient = patient; - - let dicomJsonObj = new BaseDicomJson(dicomJson); - this.study_instance_uid = dicomJsonObj.getValue("0020000D"); - this.accession_number = dicomJsonObj.getValue(dictionary.keyword.AccessionNumber); - this.accno_local_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAccessionNumberSequence}.Value.0.${dictionary.keyword.LocalNamespaceEntityID}`); - this.accno_universal_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAccessionNumberSequence}.Value.0.${dictionary.keyword.UniversalEntityID}`); - this.accno_universal_id_type = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAccessionNumberSequence}.Value.0.${dictionary.keyword.UniversalEntityIDType}`); - this.requested_procedure_id = dicomJsonObj.getValue(dictionary.keyword.RequestedProcedureID); - this.admission_id = dicomJsonObj.getValue(dictionary.keyword.AdmissionID); - this.issuer_admission_local_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.LocalNamespaceEntityID}`); - this.issuer_admission_universal_id = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.UniversalEntityID}`); - this.issuer_admission_universal_id_type = dicomJsonObj.getValue(`${dictionary.keyword.IssuerOfAdmissionIDSequence}.Value.0.${dictionary.keyword.UniversalEntityIDType}`); - // #region sps (Scheduled Procedure Step) - this.station_ae_title = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.StationAETitle}`); - this.station_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.StationName}`); - this.start_date = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStartDate}`); - this.end_date = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepEndDate}`); - this.start_time = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStartTime}`); - this.end_time = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepEndTime}`); - this.physician_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledPerformingPhysicianName}`); - this.procedure_step_location = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepLocation}`); - this.description = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepDescription}`); - this.protocol_code = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProtocolCodeSequence}`); - this.institution_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionName}`); - this.institution_department_name = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionalDepartmentName}`); - this.institution_department_type_code = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionalDepartmentTypeCodeSequence}`); - this.institution_code = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.InstitutionCodeSequence}`); - this.sps_id = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepID}`); - this.sps_status = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}`); - this.modality = dicomJsonObj.getValue(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.Modality}`); - // #endregion - } - - initJsonProperties(dicomJson) { - Object.keys({ - ...tagsNeedStore.MWL, - ...tagsNeedStore.Patient - }).forEach(key => { - let value = get(dicomJson, key); - value ? set(this.json, key, value) : undefined; - }); - } - - async save() { - let mwlItemObj = MwlItemModel.build(this.getPersistentObject()); - mwlItemObj.patient_id = this.patient.x00100020; - let [mwlItem, created] = await MwlItemModel.findOrCreate({ - where: { - [Op.and]: [ - { - sps_id: this.sps_id - }, - { - study_instance_uid: this.study_instance_uid - } - ] - }, - defaults: mwlItemObj.toJSON() - }); - let tempClonedMwlItem = cloneDeep(mwlItem); - - await this.setGeneralCode(mwlItem, dictionary.keyword.InstitutionalDepartmentTypeCodeSequence); - await this.setGeneralCode(mwlItem, dictionary.keyword.InstitutionCodeSequence); - await this.setGeneralCode(mwlItem, dictionary.keyword.ScheduledProtocolCodeSequence); - await this.setPhysicianName(mwlItem); - if (!created) { - await this.removeAllAssociationItems(tempClonedMwlItem); - mwlItem.json = { - ...mwlItem.json, - ...this.json - }; - mwlItem.changed("json", true); - } - await mwlItem.save(); - - return mwlItem; - } - - async setGeneralCode(item, tag) { - if (this[`x${tag}`]) { - let code = await DicomCodeModel.create({ - "x00080100": get(this[`x${tag}`], "00080100.Value.0", undefined), - "x00080102": get(this[`x${tag}`], "00080102.Value.0", undefined), - "x00080103": get(this[`x${tag}`], "00080103.Value.0", undefined), - "x00080104": get(this[`x${tag}`], "00080104.Value.0", undefined) - }); - let keyword = dictionary.tag[tag]; - await item[`set${keyword}`](code); - } - } - - async setPhysicianName(mwlItem) { - if (this.physician_name) { - let nameOfPhysician = await PersonNameModel.createPersonName(this.physician_name); - await mwlItem[`set${dictionary.tag["00400006"]}`](nameOfPhysician); - } - } - - async removeAllAssociationItems(upsWorkItem) { - const associationItemsNames = [ - "InstitutionalDepartmentTypeCodeSequence", - "InstitutionCodeSequence", - "ScheduledProtocolCodeSequence", - "ScheduledPerformingPhysicianName" - ]; - - for (let i = 0; i < associationItemsNames.length; i++) { - let associationItemName = associationItemsNames[i]; - let associationItem = await upsWorkItem[`get${associationItemName}`](); - if (associationItem) { - await associationItem.destroy(); - } - } - } - - getPersistentObject() { - return { - json: this.json, - study_instance_uid: this.study_instance_uid, - accession_number: this.accession_number, - accno_local_id: this.accno_local_id, - accno_universal_id: this.accno_universal_id, - accno_universal_id_type: this.accno_universal_id_type, - requested_procedure_id: this.requested_procedure_id, - admission_id: this.admission_id, - issuer_admission_local_id: this.issuer_admission_local_id, - issuer_admission_universal_id: this.issuer_admission_universal_id, - issuer_admission_universal_id_type: this.issuer_admission_universal_id_type, - station_ae_title: this.station_ae_title, - station_name: this.station_name, - start_date: this.start_date, - end_date: this.end_date, - start_time: vrValueTransform.DT(this.start_time), - end_time: vrValueTransform.DT(this.end_time), - procedure_step_location: this.procedure_step_location, - description: this.description, - institution_name: this.institution_name, - institution_department_name: this.institution_department_name, - sps_id: this.sps_id, - sps_status: this.sps_status, - modality: this.modality - }; - } -} - -module.exports.MwlItemPersistentObject = MwlItemPersistentObject; \ No newline at end of file diff --git a/models/sql/po/patient.po.js b/models/sql/po/patient.po.js deleted file mode 100644 index 67412da0..00000000 --- a/models/sql/po/patient.po.js +++ /dev/null @@ -1,80 +0,0 @@ -const moment = require("moment"); -const _ = require("lodash"); -const { PersonNameModel } = require("../models/personName.model"); -const { PatientModel } = require("../models/patient.model"); -const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); - - -class PatientPersistentObject { - constructor(dicomJson) { - this.json = {}; - Object.keys(tagsNeedStore.Patient).forEach(key => { - let value = _.get(dicomJson, key); - value ? _.set(this.json, key, value) : undefined; - }); - this.x00100010 = _.get(dicomJson, "00100010.Value.0", undefined); - this.x00100020 = _.get(dicomJson, "00100020.Value.0", ""); - this.x00100021 = _.get(dicomJson, "00100021.Value.0", ""); - this.x00100030 = _.get(dicomJson, "00100030.Value.0", ""); - this.x00100032 = _.get(dicomJson, "00100032.Value.0", ""); - this.x00100040 = _.get(dicomJson, "00100040.Value.0", ""); - this.x00102160 = _.get(dicomJson, "00102160.Value.0", ""); - this.x00104000 = _.get(dicomJson, "00104000.Value.0", ""); - this.x00880130 = _.get(dicomJson, "00880130.Value.0", ""); - this.x00880140 = _.get(dicomJson, "00880140.Value.0", ""); - } - - async createPersonName() { - return await PersonNameModel.createPersonName(this.x00100010); - } - - /** - * - * @param {PatientModel} patient - * @returns - */ - async updatePersonName(patient) { - return await PersonNameModel.updatePersonNameById(this.x00100010, patient.getDataValue("x00100010")); - } - - async createPatient() { - - let item = { - json: this.json, - x00100020: this.x00100020, - x00100021: this.x00100021, - x00100030: this.x00100030 ? this.x00100030 : undefined, - x00100032: this.x00100032 ? Number(this.x00100032) : undefined, - x00100040: this.x00100040, - x00102160: this.x00102160, - x00104000: this.x00104000, - x00880130: this.x00880130, - x00880140: this.x00880140 - }; - - let [patient, created] = await PatientModel.findOrCreate({ - where: { - x00100020: this.x00100020 - }, - defaults: item - }); - - if (created) { - let personName = await this.createPersonName(); - patient.x00100010 = personName ? personName.id : undefined; - await patient.save(); - } else { - await PatientModel.update(item, { - where: { - id: patient.dataValues.id - } - }); - await this.updatePersonName(patient); - } - - return patient; - } - -} - -module.exports.PatientPersistentObject = PatientPersistentObject; \ No newline at end of file diff --git a/models/sql/po/series.po.js b/models/sql/po/series.po.js deleted file mode 100644 index 53b8a127..00000000 --- a/models/sql/po/series.po.js +++ /dev/null @@ -1,186 +0,0 @@ -const moment = require("moment"); -const _ = require("lodash"); -const { PersonNameModel } = require("../models/personName.model"); -const { SeriesModel } = require("../models/series.model"); - -const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); -const sequelize = require("../instance"); -const { SeriesRequestAttributesModel } = require("../models/seriesRequestAttributes.model"); - -class SeriesPersistentObject { - constructor(dicomJson, study) { - - this.json = {}; - Object.keys({ - ...tagsNeedStore.Study, - ...tagsNeedStore.Series - }).forEach(key => { - let value = _.get(dicomJson, key); - value ? _.set(this.json, key, value) : undefined; - }); - this.study = study; - this.seriesPath = _.get(dicomJson, "seriesPath", ""); - - this.x0020000D = this.study.x0020000D; - this.x0020000E = _.get(dicomJson, "0020000E.Value.0", ""); - this.x00080021 = _.get(dicomJson, "00080021.Value.0", undefined); - this.x00080060 = _.get(dicomJson, "00080060.Value.0", ""); - this.x0008103E = _.get(dicomJson, "0008103E.Value.0", ""); - this.x0008103F = _.get(dicomJson, "0008103F.Value", undefined); - this.x00081050 = _.get(dicomJson, "00081050.Value", ""); - this.x00081052 = _.get(dicomJson, "00081052.Value.0", ""); - this.x00081070 = _.get(dicomJson, "00081070.Value", ""); - this.x00081072 = _.get(dicomJson, "00081072.Value", ""); - this.x00081250 = _.get(dicomJson, "00081250.Value", ""); - this.x00200011 = _.get(dicomJson, "00200011.Value.0", ""); - this.x00400244 = _.get(dicomJson, "00400244.Value.0", undefined); - this.x00400245 = _.get(dicomJson, "00400245.Value.0", ""); - this.x00400275 = _.get(dicomJson, "00400275.Value.0", ""); - this.x00080031 = _.get(dicomJson, "00080031.Value.0", ""); - } - - async createReferringPhysicianName() { - return await PersonNameModel.createPersonName(this.x00080090); - } - - async addPerformingPhysicianNames(series) { - let performingPhysicianNames = await PersonNameModel.createPersonNames(series, "x00081050"); - for (let performingPhysicianName of performingPhysicianNames) { - await series.addPerformingPhysicianName(performingPhysicianName); - } - } - - async updatePerformingPhysicianNames(series) { - // The value multiplicity of PerformingPhysicianName is 1-n - // We cannot sure the length of data is changed - // So, destroy all and recreate - for(let personName of series.performingPhysicianName) { - await personName.destroy(); - } - await this.addPerformingPhysicianNames(series); - } - - async addOperatorsNames(series) { - let operationsNames = await PersonNameModel.createPersonNames(series, "x00081070"); - for (let operationsName of operationsNames) { - await series.addOperatorsName(operationsName); - } - } - - async updateOperatorsNames(series) { - for(let personName of series.operatorsName) { - await personName.destroy(); - } - await this.addOperatorsNames(series); - } - - getRequestAttributesInJson() { - if (this.x00400275) { - return { - x0020000E: this.x0020000E, - x00080050: _.get(this.x00400275, "00080050.Value.0"), - x00080051_x00400031: _.get(this.x00400275, "00080051.Value.0.00400031.Value.0"), - x00080051_x00400032: _.get(this.x00400275, "00080051.Value.0.00400032.Value.0"), - x00080051_x00400033: _.get(this.x00400275, "00080051.Value.0.00400033.Value.0"), - x00321033: _.get(this.x00400275, "00321033.Value.0"), - x00401001: _.get(this.x00400275, "00401001.Value.0"), - x0020000D: _.get(this.x00400275, "0020000D.Value.0") - }; - } - return undefined; - } - - /** - * - * @param {SeriesModel} series - */ - async updateRequestAttribute(series) { - let requestAttributes = this.getRequestAttributesInJson(); - if (requestAttributes) { - if (await series.getSeriesRequestAttribute()) { - await SeriesRequestAttributesModel.update(requestAttributes, { - where: { - x0020000E: series.dataValues.x0020000E - } - }); - } else { - await series.createSeriesRequestAttribute(requestAttributes); - } - } else { - await SeriesRequestAttributesModel.destroy({ - where: { - x0020000E: series.dataValues.x0020000E - } - }); - } - } - - async createSeries() { - let item = { - json: this.json, - x0020000D: this.x0020000D, - x0020000E: this.x0020000E, - x00080021: this.x00080021, - x00080060: this.x00080060, - x0008103E: this.x0008103E, - x0008103F: this.x0008103F, - x00081052: this.x00081052, - x00081072: this.x00081072, - x00081250: this.x00081250, - x00200011: this.x00200011, - x00400244: this.x00400244, - x00400245: this.x00400245 ? Number(this.x00400245) : undefined, - x00080031: this.x00080031 ? Number(this.x00080031) : undefined, - seriesPath: this.seriesPath, - deleteStatus: 0 - }; - - let [series, created] = await SeriesModel.findOrCreate({ - where: { - x0020000D: this.x0020000D, - x0020000E: this.x0020000E - }, - defaults: item - }); - - if (created) { - await this.addPerformingPhysicianNames(series); - await this.addOperatorsNames(series); - let requestAttributes = this.getRequestAttributesInJson(); - if (requestAttributes) { - await series.createSeriesRequestAttribute(requestAttributes); - } - - await series.save(); - } else { - await SeriesModel.update(item, { - where: { - x0020000E: series.dataValues.x0020000E - } - }); - await this.updateRequestAttribute(series); - - let seriesWithIncludeItem = await SeriesModel.findByPk(series.dataValues.x0020000E, { - include: [ - { - model: sequelize.model("PersonName"), - as: "performingPhysicianName", - attributes: ["id"] - }, - { - model: sequelize.model("PersonName"), - as: "operatorsName", - attributes: ["id"] - } - ], - attributes: ["x0020000E"] - }); - await this.updatePerformingPhysicianNames(seriesWithIncludeItem); - } - - return series; - } - -} - -module.exports.SeriesPersistentObject = SeriesPersistentObject; \ No newline at end of file diff --git a/models/sql/po/study.po.js b/models/sql/po/study.po.js deleted file mode 100644 index ef9f050a..00000000 --- a/models/sql/po/study.po.js +++ /dev/null @@ -1,84 +0,0 @@ -const moment = require("moment"); -const _ = require("lodash"); -const { PersonNameModel } = require("../models/personName.model"); -const { StudyModel } = require("../models/study.model"); -const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); - - - -class StudyPersistentObject { - constructor(dicomJson, patient) { - - this.json = {}; - Object.keys(tagsNeedStore.Study).forEach(key => { - let value = _.get(dicomJson, key); - value ? _.set(this.json, key, value) : undefined; - }); - this.patient = patient; - this.studyPath = _.get(dicomJson, "studyPath", ""); - - this.x00080005 = _.get(dicomJson, "00080005.Value", undefined); - this.x00080020 = _.get(dicomJson, "00080020.Value.0", ""); - this.x00080030 = _.get(dicomJson, "00080030.Value.0", ""); - this.x00080050 = _.get(dicomJson, "00080050.Value.0", ""); - this.x00080056 = _.get(dicomJson, "00080056.Value.0", ""); - this.x00080090 = _.get(dicomJson, "00080090.Value.0", ""); - this.x00080201 = _.get(dicomJson, "00080201.Value.0", ""); - this.x0020000D = _.get(dicomJson, "0020000D.Value.0", ""); - this.x00200010 = _.get(dicomJson, "00200010.Value.0", ""); - this.x00201206 = _.get(dicomJson, "00201206.Value.0", ""); - this.x00201208 = _.get(dicomJson, "00201208.Value.0", ""); - } - - async createReferringPhysicianName() { - return await PersonNameModel.createPersonName(this.x00080090); - } - - async updateReferringPhysicianName(study) { - return await PersonNameModel.updatePersonNameById(this.x00080090, study.getDataValue("x00080090")); - } - - async createStudy() { - let item = { - json: this.json, - x00100020: this.patient.x00100020, - x00080005 : this.x00080005, - x00080020 : this.x00080020, - x00080030 : this.x00080030 ? Number(this.x00080030) : undefined, - x00080050 : this.x00080050, - x00080056 : this.x00080056, - x00080201 : this.x00080201, - x0020000D : this.x0020000D, - x00200010 : this.x00200010, - x00201206 : this.x00201206, - x00201208 : this.x00201208, - studyPath: this.studyPath, - deleteStatus: 0 - }; - - let [study, created] = await StudyModel.findOrCreate({ - where: { - x0020000D: this.x0020000D - }, - defaults: item - }); - - if (created) { - let referringPhysicianName = await this.createReferringPhysicianName(); - study.x00080090 = referringPhysicianName ? referringPhysicianName.id : undefined; - await study.save(); - } else { - await StudyModel.update(item, { - where: { - x0020000D: study.dataValues.x0020000D - } - }); - await this.updateReferringPhysicianName(study); - } - - return study; - } - -} - -module.exports.StudyPersistentObject = StudyPersistentObject; \ No newline at end of file diff --git a/models/sql/po/upsWorkItem.po.js b/models/sql/po/upsWorkItem.po.js deleted file mode 100644 index 928f268f..00000000 --- a/models/sql/po/upsWorkItem.po.js +++ /dev/null @@ -1,222 +0,0 @@ -const { get, set, cloneDeep, unset } = require("lodash"); -const { PersonNameModel } = require("../models/personName.model"); -const { tagsNeedStore } = require("@models/DICOM/dicom-tags-mapping"); -const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { vrValueTransform } = require("./utils"); -const { WorkItemModel } = require("../models/workitems.model"); -const { DicomCodeModel } = require("../models/dicomCode.model"); -const { UpsRequestAttributesModel } = require("../models/upsRequestAttributes.model"); -const { UpdateWorkItemService } = require("@root/api/dicom-web/controller/UPS-RS/service/update-workItem.service"); - - -class UpsWorkItemPersistentObject { - constructor(dicomJson, patient) { - - this.json = {}; - this.initJsonProperties(dicomJson); - - this.patient = patient; - - let dicomJsonObj = new BaseDicomJson(dicomJson); - this.upsInstanceUID = get(dicomJson, "upsInstanceUID", ""); - this.patientID = get(dicomJson, "patientID", ""); - this.transactionUID = get(dicomJson, "transactionUID", ""); - - this.x00100020 = dicomJsonObj.getValue("00100020"); - this.x00080018 = dicomJsonObj.getValue("00080018"); - this.x00741200 = dicomJsonObj.getValue("00741200"); - this.x00404010 = dicomJsonObj.getValue("00404010"); - this.x00741204 = dicomJsonObj.getValue("00741204"); - this.x00741202 = dicomJsonObj.getValue("00741202"); - this.x00404025 = dicomJsonObj.getValue("00404025"); - this.x00404026 = dicomJsonObj.getValue("00404026"); - this.x00404027 = dicomJsonObj.getValue("00404027"); - this.x00404005 = dicomJsonObj.getValue("00404005"); - let scheduledHumanPerformerSequence = new BaseDicomJson(dicomJsonObj.getValue("00404034")); - this.x00404009 = scheduledHumanPerformerSequence.getValue("00404009"); - this.x00404037 = scheduledHumanPerformerSequence.getValue("00404037"); - this.x00404036 = scheduledHumanPerformerSequence.getValue("00404036"); - this.x00404011 = dicomJsonObj.getValue("00404011"); - this.x00400418 = dicomJsonObj.getValue("00400418"); - this.x00380010 = dicomJsonObj.getValue("00380010"); - this.x00741000 = dicomJsonObj.getValue("00741000"); - this.x00080082 = dicomJsonObj.getValue("00080082"); - - // issuer of Admission ID - let issuerOfAdmissionIdSequence = new BaseDicomJson(dicomJsonObj.getValue("00380014")); - this.admissionLocalEntityId = issuerOfAdmissionIdSequence.getValue("00400031"); - this.admissionUniversalEntityId = issuerOfAdmissionIdSequence.getValue("00400032"); - this.admissionUniversalEntityIdType = issuerOfAdmissionIdSequence.getValue("00400033"); - } - - initJsonProperties(dicomJson) { - Object.keys({ - ...tagsNeedStore.UPS, - ...tagsNeedStore.Patient - }).forEach(key => { - let value = get(dicomJson, key); - value ? set(this.json, key, value) : undefined; - }); - } - - async save() { - let upsWorkItemObj = WorkItemModel.build(this.getPersistentObject()); - let [upsWorkItem, created] = await WorkItemModel.findOrCreate({ - where: { - upsInstanceUID: this.upsInstanceUID - }, - defaults: upsWorkItemObj.toJSON() - }); - let tempUpsWorkItem = cloneDeep(upsWorkItem); - - - await upsWorkItem.setPatient(this.patient); - await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationNameCodeSequence); - await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationClassCodeSequence); - await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledStationGeographicLocationCodeSequence); - await this.setGeneralCode(upsWorkItem, dictionary.keyword.HumanPerformerCodeSequence); - await this.setGeneralCode(upsWorkItem, dictionary.keyword.ScheduledWorkitemCodeSequence); - await this.setGeneralCode(upsWorkItem, dictionary.keyword.InstitutionCodeSequence); - await this.setHumanPerformerName(upsWorkItem); - - let requestAttributeDAO = new UpsWorkItemRequestAttributeDAO(this.upsInstanceUID, this.json); - await requestAttributeDAO.update(upsWorkItem); - - if (!created) { - await this.removeAllAssociationItems(tempUpsWorkItem); - this.adjustUpdateWorkItem(); - upsWorkItem.json = { - ...upsWorkItem.json, - ...this.json - }; - upsWorkItem.changed("json", true); - } - await upsWorkItem.save(); - - return upsWorkItem; - } - - async setGeneralCode(item, tag) { - if (this[`x${tag}`]) { - let code = await DicomCodeModel.create({ - "x00080100": get(this[`x${tag}`], "00080100.Value.0", undefined), - "x00080102": get(this[`x${tag}`], "00080102.Value.0", undefined), - "x00080103": get(this[`x${tag}`], "00080103.Value.0", undefined), - "x00080104": get(this[`x${tag}`], "00080104.Value.0", undefined) - }); - let keyword = dictionary.tag[tag]; - await item[`set${keyword}`](code); - } - } - - async setHumanPerformerName(upsWorkItem) { - if (this.x00404037) { - let nameOfHumanPerformer = await PersonNameModel.createPersonName(this.x00404037); - await upsWorkItem[`set${dictionary.tag["00404037"]}`](nameOfHumanPerformer); - } - } - - async removeAllAssociationItems(upsWorkItem) { - const associationItemsNames = [ - "ScheduledStationNameCodeSequence", - "ScheduledStationClassCodeSequence", - "ScheduledStationGeographicLocationCodeSequence", - "HumanPerformerCodeSequence", - "ScheduledWorkitemCodeSequence", - "InstitutionCodeSequence", - "HumanPerformerName" - ]; - - for (let i = 0 ; i < associationItemsNames.length ; i++) { - let associationItemName = associationItemsNames[i]; - let associationItem = await upsWorkItem[`get${associationItemName}`](); - if (associationItem) { - await associationItem.destroy(); - } - } - } - - getPersistentObject() { - return { - json: this.json, - upsInstanceUID : this.upsInstanceUID, - patientID : this.patientID, - transactionUID : this.transactionUID, - x00100020: this.x00100020, - x00741200: this.x00741200, - x00404010: vrValueTransform.DT(this.x00404010), - x00741204: this.x00741204, - x00741202: this.x00741202, - x00404036: this.x00404036, - x00404005: vrValueTransform.DT(this.x00404005), - x00404011: vrValueTransform.DT(this.x00404011), - x00380010: this.x00380010, - x00741000: this.x00741000, - x00380014_x00400031: this.admissionLocalEntityId, - x00380014_x00400032: this.admissionUniversalEntityId, - x00380014_x00400033: this.admissionUniversalEntityIdType - }; - } - - adjustUpdateWorkItem() { - for (let i = 0; i < UpdateWorkItemService.notAllowedAttributes.length; i++) { - let notAllowedAttr = UpdateWorkItemService.notAllowedAttributes[i]; - unset(this.json, notAllowedAttr); - } - } -} - -class UpsWorkItemRequestAttributeDAO { - constructor(upsInstanceUID, dicomJson) { - this.dicomJson = dicomJson; - this.dicomJsonObj = new BaseDicomJson(this.dicomJson); - this.upsInstanceUID = upsInstanceUID; - } - - async getRequestAttribute() { - let requestAttribute = this.dicomJsonObj.getValue(dictionary.keyword.ReferencedRequestSequence); - if (requestAttribute) { - return { - upsInstanceUID: this.upsInstanceUID, - x00080050: get(requestAttribute, "00080050.Value.0"), - x00080051_x00400031: get(requestAttribute, "00080051.Value.0.00400031.Value.0"), - x00080051_x00400032: get(requestAttribute, "00080051.Value.0.00400032.Value.0"), - x00080051_x00400033: get(requestAttribute, "00080051.Value.0.00400033.Value.0"), - x00321033: get(requestAttribute, "00321033.Value.0"), - x00401001: get(requestAttribute, "00401001.Value.0"), - x0020000D: get(requestAttribute, "0020000D.Value.0") - }; - } - - return undefined; - } - - - /** - * - * @param {WorkItemModel} workItem - */ - async update(workItem) { - let requestAttributes = await this.getRequestAttribute(); - if (requestAttributes) { - if (await workItem.getUpsRequestAttributesModel()) { - await UpsRequestAttributesModel.update(requestAttributes, { - where: { - upsInstanceUID: workItem.upsInstanceUID - } - }); - } else { - await workItem.createUpsRequestAttributesModel(requestAttributes); - } - } else { - await UpsRequestAttributesModel.destroy({ - where: { - upsInstanceUID: workItem.upsInstanceUID - } - }); - } - } -} - -module.exports.UpsWorkItemPersistentObject = UpsWorkItemPersistentObject; \ No newline at end of file diff --git a/models/sql/po/utils.js b/models/sql/po/utils.js deleted file mode 100644 index 0edd878a..00000000 --- a/models/sql/po/utils.js +++ /dev/null @@ -1,7 +0,0 @@ -const moment = require("moment"); - -const vrValueTransform = { - "DT": (v) => v ? moment(v, "YYYYMMDDhhmmss.SSSSSSZZ").toISOString(): undefined -}; - -module.exports.vrValueTransform = vrValueTransform; \ No newline at end of file diff --git a/models/sql/vrTypeMapping.js b/models/sql/vrTypeMapping.js deleted file mode 100644 index 9ecd7349..00000000 --- a/models/sql/vrTypeMapping.js +++ /dev/null @@ -1,40 +0,0 @@ -const { raccoonConfig } = require("@root/config-class"); -const { DataTypes } = require("sequelize"); - -const vrTypeMapping = { - "AE": DataTypes.STRING, - "AS": DataTypes.STRING, - "CS": DataTypes.STRING, - "DA": DataTypes.DATEONLY, - "DS": DataTypes.STRING, - "DT": DataTypes.DATE, - "FT": DataTypes.FLOAT, - "FD": DataTypes.DOUBLE, - "IS": DataTypes.STRING, - "LO": DataTypes.STRING, - "LT": DataTypes.STRING(10240+1), - "OB": DataTypes.TEXT, - "OD": DataTypes.TEXT, - "OF": DataTypes.TEXT, - "OL": DataTypes.TEXT, - "OV": DataTypes.TEXT, - "OW": DataTypes.TEXT, - "PN": DataTypes.INTEGER, // foreign key - "SH": DataTypes.STRING, - "SL": DataTypes.INTEGER, - "SS": DataTypes.SMALLINT, - "ST": DataTypes.STRING(1024+1), - "SV": DataTypes.BIGINT, - "TM": DataTypes.DECIMAL, - "UC": DataTypes.TEXT("long"), - "UI": DataTypes.STRING, - "UL": DataTypes.INTEGER.UNSIGNED, - "UR": DataTypes.TEXT("long"), - "US": DataTypes.SMALLINT.UNSIGNED, - "UT": DataTypes.TEXT("long"), - "UV": DataTypes.BIGINT.UNSIGNED, - "JSON": raccoonConfig.dbConfig.dialect === "postgres" ? DataTypes.JSONB : DataTypes.JSON // For Array or SQ data -}; - - -module.exports.vrTypeMapping = vrTypeMapping; \ No newline at end of file From b5551ce47580842ce678e25c0e598e8ae9fac2bb Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 16:53:09 +0800 Subject: [PATCH 327/365] docs: add types for `dicom-json-model` --- models/DICOM/dicom-json-model.js | 29 +++++++++++++++++++++++++++++ utils/typeDef/STOW-RS/STOW-RS.d.ts | 25 ++++++++++++++----------- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 8100cc33..a507504b 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -159,6 +159,10 @@ class DicomJsonModel { }; } + /** + * + * @param {import("@root/utils/typeDef/STOW-RS/STOW-RS").DicomFileSaveInfo} dicomFileSaveInfo + */ async storeToDb(dicomFileSaveInfo) { let dbJson = this.getCleanDataBeforeStoringToDb(dicomFileSaveInfo); try { @@ -173,6 +177,11 @@ class DicomJsonModel { } } + /** + * + * @param {import("@root/utils/typeDef/STOW-RS/STOW-RS").DicomFileSaveInfo} dicomFileSaveInfo + * @returns {import("@root/utils/typeDef/dicom").GeneralDicomJson} + */ getCleanDataBeforeStoringToDb(dicomFileSaveInfo) { let dicomJsonClone = _.cloneDeep(this.dicomJson); let mediaStorage = this.getMediaStorageInfo(); @@ -189,6 +198,10 @@ class DicomJsonModel { return dicomJsonClone; } + /** + * + * @param {import("@root/utils/typeDef/dicom").GeneralDicomJson} dicomJson + */ async storeInstanceCollection(dicomJson) { let query = { $and: [ @@ -210,6 +223,10 @@ class DicomJsonModel { }); } + /** + * + * @param {import("@root/utils/typeDef/dicom").GeneralDicomJson} dicomJson + */ async storeStudyCollection(dicomJson) { await mongoose.model("dicomStudy").findOneAndUpdate( { @@ -223,6 +240,10 @@ class DicomJsonModel { ); } + /** + * + * @param {import("@root/utils/typeDef/dicom").GeneralDicomJson} dicomJson + */ async storeSeriesCollection(dicomJson) { await mongoose.model("dicomSeries").findOneAndUpdate( { @@ -243,6 +264,10 @@ class DicomJsonModel { ); } + /** + * + * @param {import("@root/utils/typeDef/dicom").GeneralDicomJson} dicomJson + */ async storePatientCollection(dicomJson) { await PatientModel.findOneAndUpdate( { @@ -261,6 +286,10 @@ class DicomJsonModel { ); } + /** + * + * @param {string} storeFullPath + */ async saveMetadataToFile(storeFullPath) { try { let dicomJsonClone = _.cloneDeep(this.dicomJson); diff --git a/utils/typeDef/STOW-RS/STOW-RS.d.ts b/utils/typeDef/STOW-RS/STOW-RS.d.ts index 569888e6..6f75bb0c 100644 --- a/utils/typeDef/STOW-RS/STOW-RS.d.ts +++ b/utils/typeDef/STOW-RS/STOW-RS.d.ts @@ -12,21 +12,24 @@ export type MultipartParseResult = { multipart: Multipart; }; -export type SaveDicomFileResult = { - /** The path of saved file's directory */ - fullPath: string; +export type DicomFileSaveInfo = { + /** The path of saved file's directory */ + fullPath: string; - /** The relative path of saved DICOM instance file. e.g. /files/123.dcm */ - relativePath: string; + /** The relative path of saved DICOM instance file. e.g. /files/123.dcm */ + relativePath: string; - /** The full path of saved DICOM instance file. e.g. /home/app/files/123.dcm */ - instancePath: string; + /** The full path of saved DICOM instance file. e.g. /home/app/files/123.dcm */ + instancePath: string; - /** The relative path of series level directory */ - seriesPath: string; + /** The relative path of series level directory */ + seriesPath: string; - /** The relative path of study level directory */ - studyPath: string; + /** The relative path of study level directory */ + studyPath: string; +}; + +export type SaveDicomFileResult = DicomFileSaveInfo & { dicomJson: GeneralDicomJson; }; From 3acda03c9a6bea6ac142678e4ebd388e5f5c2063 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 16:56:27 +0800 Subject: [PATCH 328/365] refactor: rename to `instanceUID` of `sopInstanceUID` for more consistent --- .../STOW-RS/service/dicom-fhir.service.js | 6 ++--- .../STOW-RS/service/dicom-jpeg-generator.js | 6 ++--- .../STOW-RS/service/stow-rs.service.js | 6 ++--- models/DICOM/dicom-json-model.js | 22 +++++++++---------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/dicom-web/controller/STOW-RS/service/dicom-fhir.service.js b/api/dicom-web/controller/STOW-RS/service/dicom-fhir.service.js index c86f2740..b06d8cec 100644 --- a/api/dicom-web/controller/STOW-RS/service/dicom-fhir.service.js +++ b/api/dicom-web/controller/STOW-RS/service/dicom-fhir.service.js @@ -41,7 +41,7 @@ class DicomFhirService { let logObj = { studyUID: this.dicomJsonModel.uidObj.studyUID, seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + instanceUID: this.dicomJsonModel.uidObj.instanceUID, status: true, message: "success" }; @@ -56,7 +56,7 @@ class DicomFhirService { let errorLogObj = { studyUID: this.dicomJsonModel.uidObj.studyUID, seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + instanceUID: this.dicomJsonModel.uidObj.instanceUID, status: false, message: errorStr }; @@ -81,7 +81,7 @@ class DicomFhirService { seriesUID: this.dicomJsonModel.uidObj.seriesUID }, { - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID + instanceUID: this.dicomJsonModel.uidObj.instanceUID } ] }, diff --git a/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js b/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js index 55215360..dff2b8b7 100644 --- a/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js +++ b/api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js @@ -98,7 +98,7 @@ class DicomJpegGenerator { let startTaskObj = { studyUID: this.dicomJsonModel.uidObj.studyUID, seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + instanceUID: this.dicomJsonModel.uidObj.instanceUID, status: false, message: "processing", taskTime: new Date(), @@ -116,7 +116,7 @@ class DicomJpegGenerator { let endTaskObj = { studyUID: this.dicomJsonModel.uidObj.studyUID, seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + instanceUID: this.dicomJsonModel.uidObj.instanceUID, status: true, message: "generated", finishedTime: new Date() @@ -132,7 +132,7 @@ class DicomJpegGenerator { let errorTaskObj = { studyUID: this.dicomJsonModel.uidObj.studyUID, seriesUID: this.dicomJsonModel.uidObj.seriesUID, - instanceUID: this.dicomJsonModel.uidObj.sopInstanceUID, + instanceUID: this.dicomJsonModel.uidObj.instanceUID, status: false, message: message, finishedTime: new Date() diff --git a/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js index 241ff27b..e9b9cae5 100644 --- a/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ b/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -170,7 +170,7 @@ class StowRsService { this.responseMessage["00081190"].Value.push(retrieveUrlObj.study); this.responseMessage["00081190"].Value = _.uniq(this.responseMessage["00081190"].Value); - let sopSeq = this.getSOPSeq(dicomJsonModel.uidObj.sopClass, dicomJsonModel.uidObj.sopInstanceUID); + let sopSeq = this.getSOPSeq(dicomJsonModel.uidObj.sopClass, dicomJsonModel.uidObj.instanceUID); _.set(sopSeq, "00081190.vr", "UT"); _.set(sopSeq, "00081190.Value", [retrieveUrlObj.instance]); this.responseMessage["00081199"]["Value"].push(sopSeq); @@ -195,7 +195,7 @@ class StowRsService { isSameStudyID_(uidObj, storeMessage) { let reqStudyId = this.request.params.studyID; let dataStudyId = uidObj.studyUID; - let sopSeq = this.getSOPSeq(uidObj.sopClass, uidObj.sopInstanceUID); + let sopSeq = this.getSOPSeq(uidObj.sopClass, uidObj.instanceUID); let result = true; if (reqStudyId) { @@ -262,7 +262,7 @@ class StowRsService { return { study: `${url}/${uidObj.studyUID}`, series: `${url}/${uidObj.studyUID}/series/${uidObj.seriesUID}`, - instance: `${url}/${uidObj.studyUID}/series/${uidObj.seriesUID}/instances/${uidObj.sopInstanceUID}` + instance: `${url}/${uidObj.studyUID}/series/${uidObj.seriesUID}/instances/${uidObj.instanceUID}` }; } diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index a507504b..076282e7 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -140,7 +140,7 @@ class DicomJsonModel { this.dicomJson, "00080016" ), - sopInstanceUID: dcm2jsonV8.dcmString( + instanceUID: dcm2jsonV8.dcmString( this.dicomJson, "00080018" ), @@ -194,7 +194,7 @@ class DicomJsonModel { _.merge(dicomJsonClone, mediaStorage); delete dicomJsonClone.sopClass; - delete dicomJsonClone.sopInstanceUID; + delete dicomJsonClone.instanceUID; return dicomJsonClone; } @@ -212,7 +212,7 @@ class DicomJsonModel { seriesUID: this.uidObj.seriesUID }, { - instanceUID: this.uidObj.sopInstanceUID + instanceUID: this.uidObj.instanceUID } ] }; @@ -504,7 +504,7 @@ class DicomJsonBinaryDataModel { let { studyUID, seriesUID, - sopInstanceUID + instanceUID } = this.dicomJsonModel.uidObj; for (let i = 0; i < this.binaryKeys.length; i++) { @@ -522,7 +522,7 @@ class DicomJsonBinaryDataModel { _.set( this.dicomJsonModel.dicomJson, `${binaryKey}.BulkDataURI`, - `/studies/${studyUID}/series/${seriesUID}/instances/${sopInstanceUID}/bulkdata/${pathOfBinaryProperty}` + `/studies/${studyUID}/series/${seriesUID}/instances/${instanceUID}/bulkdata/${pathOfBinaryProperty}` ); _.unset(this.dicomJsonModel.dicomJson, `${binaryKey}.InlineBinary`); @@ -530,16 +530,16 @@ class DicomJsonBinaryDataModel { this.dicomJsonModel.dicomJson["7FE00010"] = { vr: "UR", - BulkDataURI: `/studies/${studyUID}/series/${seriesUID}/instances/${sopInstanceUID}` + BulkDataURI: `/studies/${studyUID}/series/${seriesUID}/instances/${instanceUID}` }; } async storeAllBinaryDataToFileAndDb() { let { - sopInstanceUID + instanceUID } = this.dicomJsonModel.uidObj; - let shortInstanceUID = shortHash(sopInstanceUID); + let shortInstanceUID = shortHash(instanceUID); for (let i = 0; i < this.pathGroupOfBinaryProperties.length; i++) { @@ -587,20 +587,20 @@ class BulkData { let item = { studyUID: this.uidObj.studyUID, seriesUID: this.uidObj.seriesUID, - instanceUID: this.uidObj.sopInstanceUID, + instanceUID: this.uidObj.instanceUID, filename: this.filename, binaryValuePath: this.pathOfBinaryProperty }; await dicomBulkDataModel.createOrUpdateBulkData( { - instanceUID: this.uidObj.sopInstanceUID, + instanceUID: this.uidObj.instanceUID, binaryValuePath: this.pathOfBinaryProperty }, { studyUID: this.uidObj.studyUID, seriesUID: this.uidObj.seriesUID, - instanceUID: this.uidObj.sopInstanceUID, + instanceUID: this.uidObj.instanceUID, filename: this.filename, binaryValuePath: this.pathOfBinaryProperty } From fc859ac48bb60a19e44c03a7c2c3805f49725d5b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 17:02:21 +0800 Subject: [PATCH 329/365] fix: `PatientModel` not load in `dicom-json-model` - use `mongoose.model` instead --- models/DICOM/dicom-json-model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 076282e7..5c9136ac 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -269,7 +269,7 @@ class DicomJsonModel { * @param {import("@root/utils/typeDef/dicom").GeneralDicomJson} dicomJson */ async storePatientCollection(dicomJson) { - await PatientModel.findOneAndUpdate( + await mongoose.model("patient").findOneAndUpdate( { patientID: this.uidObj.patientID }, From 865273e08e6b9f0bcc55b2b9725c25478d0fc95b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 17:25:05 +0800 Subject: [PATCH 330/365] refactor: move `getUidsString` into `DicomWebService` --- .../WADO-RS/service/WADO-RS.service.js | 18 +----------------- .../WADO-RS/service/thumbnail.service.js | 4 ++-- api/dicom-web/service/dicom-web.service.js | 17 ++++++++++++++++- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 74195d88..5d7fd55f 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -98,7 +98,7 @@ class ImagePathFactory { return { status: false, code: 404, - message: `not found, ${getUidsString(this.uids)}` + message: `not found, ${DicomWebService.getUidsString(this.uids)}` }; } @@ -245,21 +245,6 @@ function addHostnameOfBulkDataUrl(metadata, req) { } } -/** -* -* @param {Pick} uids -* @returns -*/ -function getUidsString(uids) { - let uidsKeys = Object.keys(uids); - let strArr = []; - for (let i = 0; i < uidsKeys.length; i++) { - let key = uidsKeys[i]; - strArr.push(`${key}: ${uids[key]}`); - } - return strArr.join(", "); -} - module.exports.getAcceptType = getAcceptType; module.exports.supportInstanceMultipartType = supportInstanceMultipartType; module.exports.sendNotSupportedMediaType = sendNotSupportedMediaType; @@ -270,4 +255,3 @@ module.exports.SeriesImagePathFactory = SeriesImagePathFactory; module.exports.InstanceImagePathFactory = InstanceImagePathFactory; module.exports.multipartContentTypeWriter = multipartContentTypeWriter; module.exports.ImageMultipartWriter = ImageMultipartWriter; -module.exports.getUidsString = getUidsString; diff --git a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js index 3caddd4c..9a676e30 100644 --- a/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js +++ b/api/dicom-web/controller/WADO-RS/service/thumbnail.service.js @@ -2,7 +2,7 @@ const { InstanceModel } = require("@dbModels/instance.model"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const renderedService = require("@api/dicom-web/controller/WADO-RS/service/rendered.service"); const _ = require("lodash"); -const { getUidsString } = require("./WADO-RS.service"); +const { DicomWebService } = require("@api/dicom-web/service/dicom-web.service"); const { NotFoundInstanceError } = require("@error/dicom-instance"); class ThumbnailService { @@ -56,7 +56,7 @@ class ThumbnailService { checkInstanceExists(instanceFramesObj) { if (!instanceFramesObj) { - throw new NotFoundInstanceError(`Not Found, ${getUidsString(this.thumbnailFactory.uids)}`); + throw new NotFoundInstanceError(`Not Found, ${DicomWebService.getUidsString(this.thumbnailFactory.uids)}`); } } } diff --git a/api/dicom-web/service/dicom-web.service.js b/api/dicom-web/service/dicom-web.service.js index e391d996..225f311d 100644 --- a/api/dicom-web/service/dicom-web.service.js +++ b/api/dicom-web/service/dicom-web.service.js @@ -14,7 +14,7 @@ class DicomWebService { this.protocol = req.secure ? "https" : "http"; } - + getBasicURL() { let hostname = raccoonConfig.dicomWebConfig.host; @@ -53,6 +53,21 @@ class DicomWebService { static getServerHostname() { return `${raccoonConfig.serverConfig.host}`; } + + /** + * + * @param {Pick} uids + * @returns + */ + static getUidsString(uids) { + let uidsKeys = Object.keys(uids); + let strArr = []; + for (let i = 0; i < uidsKeys.length; i++) { + let key = uidsKeys[i]; + strArr.push(`${key}: ${uids[key]}`); + } + return strArr.join(", "); + } } module.exports.DicomWebService = DicomWebService; \ No newline at end of file From 1f862b64bd932ae50f39fe6e12f10e225e8bbdb0 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 20:42:13 +0800 Subject: [PATCH 331/365] refactor: wrap update method `updateOneByUpsInstanceUID` in work item model --- .../UPS-RS/service/change-workItem-state.service.js | 6 +----- .../UPS-RS/service/update-workItem.service.js | 6 +----- models/mongodb/models/workitems.model.js | 11 ++++++++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index b422f73c..23a41fb0 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -52,12 +52,8 @@ class ChangeWorkItemStateService extends BaseWorkItemService { this.completeChange(); } - let updatedWorkItem = await WorkItemModel.findOneAndUpdate({ - upsInstanceUID: this.request.params.workItem - }, { + let updatedWorkItem = await WorkItemModel.updateOneByUpsInstanceUID(this.request.params.workItem, { ...this.requestState.dicomJson - }, { - new: true }); let updatedWorkItemDicomJson = await updatedWorkItem.toDicomJson(); diff --git a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js index f48227bd..16929f87 100644 --- a/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/update-workItem.service.js @@ -48,12 +48,8 @@ class UpdateWorkItemService extends BaseWorkItemService { await this.checkRequestUpsIsValid(); this.adjustRequestWorkItem(); - let updatedWorkItem = await WorkItemModel.findOneAndUpdate({ - upsInstanceUID: this.request.params.workItem - }, { + let updatedWorkItem = await WorkItemModel.updateOneByUpsInstanceUID(this.request.params.workItem, { ...this.requestWorkItem.dicomJson - }, { - new: true }); this.triggerUpdateWorkItemEvent(updatedWorkItem); diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index fea097b6..f4f9ef78 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -91,7 +91,16 @@ let workItemSchema = new mongoose.Schema( return await mongoose.model("workItems").countDocuments({ ...$match }); - + }, + /** + * + * @param {string} upsInstanceUID + * @param {import("@root/utils/typeDef/dicom").GeneralDicomJson} generalDicomJson + */ + updateOneByUpsInstanceUID: async function (upsInstanceUID, generalDicomJson) { + return await mongoose.model("workItems").findOneAndUpdate({ + upsInstanceUID + }, generalDicomJson, {new: true}).exec(); } }, methods: { From 9d0ca8dbf51e49618be7ceda00d082a839870b95 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 20:54:06 +0800 Subject: [PATCH 332/365] refactor: wrap `unsubscribe` in ups subscription model --- .../UPS-RS/service/unsubscribe.service.js | 15 ++++----------- models/mongodb/models/upsSubscription.js | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index 4cb02b72..fb9b8fe0 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -60,20 +60,11 @@ class UnSubscribeService extends BaseWorkItemService { * @param {any} workItem repository workItem */ async deleteSubscription(workItem) { - - await UpsSubscriptionModel.findOneAndUpdate({ - aeTitle: this.subscriberAeTitle, - workItems: workItem._id - }, { - $pull: { - workItems: workItem._id - } - }); - + await UpsSubscriptionModel.unsubscribe(this.subscriberAeTitle, workItem); } async deleteGlobalSubscription() { - + // TODO: wrap method in model await Promise.all([ UpsSubscriptionModel.findOneAndDelete({ aeTitle: this.subscriberAeTitle @@ -86,12 +77,14 @@ class UnSubscribeService extends BaseWorkItemService { } async isSubscriptionExist() { + // TODO: wrap method in model return await UpsSubscriptionModel.countDocuments({ aeTitle: this.subscriberAeTitle }) > 0; } async isGlobalSubscriptionExist() { + // TODO: wrap method in model return await UpsGlobalSubscriptionModel.countDocuments({ aeTitle: this.subscriberAeTitle }) > 0; diff --git a/models/mongodb/models/upsSubscription.js b/models/mongodb/models/upsSubscription.js index 6fb372dd..6a139cb2 100644 --- a/models/mongodb/models/upsSubscription.js +++ b/models/mongodb/models/upsSubscription.js @@ -65,6 +65,22 @@ let upsSubscriptionSchema = new mongoose.Schema( workItems: workItem._id } }); + }, + /** + * + * @param {string} aeTitle + * @param {any} workItem repository item + */ + unsubscribe: async function(aeTitle, workItem) { + return await mongoose.model("upsSubscription").findOneAndUpdate({ + aeTitle: aeTitle, + workItems: workItem._id + }, { + $pull: { + workItems: workItem._id + } + }); + } } } From f5bf0532f45c6437d763c08e58aaeb6e931d3b7a Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 20:59:28 +0800 Subject: [PATCH 333/365] refactor: wrap `getCountByAeTitle` for (global)ups subscription model --- .../UPS-RS/service/unsubscribe.service.js | 10 ++------- .../mongodb/models/upsGlobalSubscription.js | 18 +++++++++++----- models/mongodb/models/upsSubscription.js | 21 ++++++++++++------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index fb9b8fe0..a3db3d9d 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -77,17 +77,11 @@ class UnSubscribeService extends BaseWorkItemService { } async isSubscriptionExist() { - // TODO: wrap method in model - return await UpsSubscriptionModel.countDocuments({ - aeTitle: this.subscriberAeTitle - }) > 0; + return await UpsSubscriptionModel.getCountByAeTitle(this.subscriberAeTitle) > 0; } async isGlobalSubscriptionExist() { - // TODO: wrap method in model - return await UpsGlobalSubscriptionModel.countDocuments({ - aeTitle: this.subscriberAeTitle - }) > 0; + return await UpsGlobalSubscriptionModel.getCountByAeTitle(this.subscriberAeTitle) > 0; } } diff --git a/models/mongodb/models/upsGlobalSubscription.js b/models/mongodb/models/upsGlobalSubscription.js index 424a3b8c..4158a872 100644 --- a/models/mongodb/models/upsGlobalSubscription.js +++ b/models/mongodb/models/upsGlobalSubscription.js @@ -2,7 +2,7 @@ const path = require("path"); const mongoose = require("mongoose"); const _ = require("lodash"); const { - SUBSCRIPTION_STATE + SUBSCRIPTION_STATE } = require("../../DICOM/ups"); let upsGlobalSubscriptionSchema = new mongoose.Schema( @@ -36,17 +36,25 @@ let upsGlobalSubscriptionSchema = new mongoose.Schema( getCursor: async function (query, options) { return await mongoose.model("upsGlobalSubscription").find(query, options).cursor(); }, - createGlobalSubscription: async function(globalSubscription) { + createGlobalSubscription: async function (globalSubscription) { return await mongoose.model("upsGlobalSubscription").create(globalSubscription); }, - updateRepositoryInstance: async function(globalSubscription, query, deletionLock ,subscribed) { + updateRepositoryInstance: async function (globalSubscription, query, deletionLock, subscribed) { globalSubscription.isDeletionLock = deletionLock; globalSubscription.subscribed = subscribed; globalSubscription.queryKeys = query; return await globalSubscription.save(); }, - findOneByAeTitle: async function(aeTitle) { - return await mongoose.model("upsGlobalSubscription").findOne({aeTitle: aeTitle}); + findOneByAeTitle: async function (aeTitle) { + return await mongoose.model("upsGlobalSubscription").findOne({ aeTitle: aeTitle }); + }, + /** + * + * @param {string} aeTitle + * @returns + */ + getCountByAeTitle: async function (aeTitle) { + return await mongoose.model("upsGlobalSubscription").countDocuments({ aeTitle: aeTitle }); } } } diff --git a/models/mongodb/models/upsSubscription.js b/models/mongodb/models/upsSubscription.js index 6a139cb2..250adcc1 100644 --- a/models/mongodb/models/upsSubscription.js +++ b/models/mongodb/models/upsSubscription.js @@ -33,18 +33,18 @@ let upsSubscriptionSchema = new mongoose.Schema( getters: true }, statics: { - findByWorkItem: async function(workItem) { - return await mongoose.model("upsSubscription").find({workItems: workItem._id}).exec(); + findByWorkItem: async function (workItem) { + return await mongoose.model("upsSubscription").find({ workItems: workItem._id }).exec(); }, /** * * @param {string} aeTitle * @returns repository item */ - findOneByAeTitle: async function(aeTitle) { + findOneByAeTitle: async function (aeTitle) { return await mongoose.model("upsSubscription").findOne({ aeTitle }).exec(); }, - createSubscriptionForWorkItem: async function(workItem, aeTitle, deletionLock, subscribed) { + createSubscriptionForWorkItem: async function (workItem, aeTitle, deletionLock, subscribed) { let subscription = new mongoose.model("upsSubscription")({ aeTitle: aeTitle, workItems: [workItem._id], @@ -53,7 +53,7 @@ let upsSubscriptionSchema = new mongoose.Schema( }); return await subscription.save(); }, - updateSubscription: async function(subscription, workItem, deletionLock, subscribed) { + updateSubscription: async function (subscription, workItem, deletionLock, subscribed) { return await mongoose.model("upsSubscription").findOneAndUpdate({ _id: subscription._id }, { @@ -71,7 +71,7 @@ let upsSubscriptionSchema = new mongoose.Schema( * @param {string} aeTitle * @param {any} workItem repository item */ - unsubscribe: async function(aeTitle, workItem) { + unsubscribe: async function (aeTitle, workItem) { return await mongoose.model("upsSubscription").findOneAndUpdate({ aeTitle: aeTitle, workItems: workItem._id @@ -80,7 +80,14 @@ let upsSubscriptionSchema = new mongoose.Schema( workItems: workItem._id } }); - + + }, + /** + * + * @param {string} aeTitle + */ + getCountByAeTitle: async function (aeTitle) { + return await mongoose.model("upsSubscription").countDocuments({ aeTitle: aeTitle }); } } } From 306c46f93c4b8c790725194474f3da59a825bad2 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 21:05:23 +0800 Subject: [PATCH 334/365] refactor: wrap `deleteOneByAeTitle` for (global)ups subscription model --- .../UPS-RS/service/suspend-subscription.service.js | 9 ++------- .../controller/UPS-RS/service/unsubscribe.service.js | 9 ++------- models/mongodb/models/upsGlobalSubscription.js | 3 +++ models/mongodb/models/upsSubscription.js | 3 +++ 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js index 4ac7186f..038e04f6 100644 --- a/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js +++ b/api/dicom-web/controller/UPS-RS/service/suspend-subscription.service.js @@ -35,17 +35,12 @@ class SuspendSubscribeService extends BaseWorkItemService { } async deleteGlobalSubscription() { - - await UpsGlobalSubscriptionModel.findOneAndDelete({ - aeTitle: this.subscriberAeTitle - }); + await UpsGlobalSubscriptionModel.deleteOneByAeTitle(this.subscriberAeTitle); } async isGlobalSubscriptionExist() { - return await UpsGlobalSubscriptionModel.countDocuments({ - aeTitle: this.subscriberAeTitle - }) > 0; + return await UpsGlobalSubscriptionModel.getCountByAeTitle(this.subscriberAeTitle) > 0; } } diff --git a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js index a3db3d9d..e0d8b816 100644 --- a/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/unsubscribe.service.js @@ -64,14 +64,9 @@ class UnSubscribeService extends BaseWorkItemService { } async deleteGlobalSubscription() { - // TODO: wrap method in model await Promise.all([ - UpsSubscriptionModel.findOneAndDelete({ - aeTitle: this.subscriberAeTitle - }), - UpsGlobalSubscriptionModel.findOneAndDelete({ - aeTitle: this.subscriberAeTitle - }) + UpsSubscriptionModel.deleteOneByAeTitle(this.subscriberAeTitle), + UpsGlobalSubscriptionModel.deleteOneByAeTitle(this.subscriberAeTitle) ]); } diff --git a/models/mongodb/models/upsGlobalSubscription.js b/models/mongodb/models/upsGlobalSubscription.js index 4158a872..af796b04 100644 --- a/models/mongodb/models/upsGlobalSubscription.js +++ b/models/mongodb/models/upsGlobalSubscription.js @@ -55,6 +55,9 @@ let upsGlobalSubscriptionSchema = new mongoose.Schema( */ getCountByAeTitle: async function (aeTitle) { return await mongoose.model("upsGlobalSubscription").countDocuments({ aeTitle: aeTitle }); + }, + deleteOneByAeTitle: async function (aeTitle) { + return await mongoose.model("upsGlobalSubscription").findOneAndDelete({ aeTitle: aeTitle }); } } } diff --git a/models/mongodb/models/upsSubscription.js b/models/mongodb/models/upsSubscription.js index 250adcc1..9149b688 100644 --- a/models/mongodb/models/upsSubscription.js +++ b/models/mongodb/models/upsSubscription.js @@ -88,6 +88,9 @@ let upsSubscriptionSchema = new mongoose.Schema( */ getCountByAeTitle: async function (aeTitle) { return await mongoose.model("upsSubscription").countDocuments({ aeTitle: aeTitle }); + }, + deleteOneByAeTitle: async function (aeTitle) { + return await mongoose.model("upsSubscription").findOneAndDelete({ aeTitle: aeTitle }); } } } From ee2ec31938dd1a6f7f4a95cd81013d61029dff7c Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Thu, 18 Jan 2024 21:20:53 +0800 Subject: [PATCH 335/365] feat: add `QueryUpsDicomJsonFactory` and using in `geWorkItemService` --- .../QIDO-RS/service/query-dicom-json-factory.js | 9 +++++++++ .../controller/UPS-RS/service/get-workItem.service.js | 10 ++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index 3a020c86..93e37f23 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -52,6 +52,7 @@ function getWildCardQuery(value) { } /** + * TODO: separate this function to single file * convert all request query object to to $or query and push to $and query * @param {Object} iQuery * @returns @@ -204,9 +205,17 @@ class QueryInstanceDicomJsonFactory extends QueryDicomJsonFactory { } } +class QueryUpsDicomJsonFactory extends QueryDicomJsonFactory { + constructor(queryOptions) { + super(queryOptions); + this.model = require("@dbModels/workitems.model").WorkItemModel; + } +} + module.exports.QueryDicomJsonFactory = QueryDicomJsonFactory; module.exports.QueryPatientDicomJsonFactory = QueryPatientDicomJsonFactory; module.exports.QueryStudyDicomJsonFactory = QueryStudyDicomJsonFactory; module.exports.QuerySeriesDicomJsonFactory = QuerySeriesDicomJsonFactory; module.exports.QueryInstanceDicomJsonFactory = QueryInstanceDicomJsonFactory; +module.exports.QueryUpsDicomJsonFactory = QueryUpsDicomJsonFactory; module.exports.convertRequestQueryToMongoQuery = convertRequestQueryToMongoQuery; diff --git a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js index 1b90158d..17f9c561 100644 --- a/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/get-workItem.service.js @@ -1,7 +1,6 @@ const _ = require("lodash"); -const { WorkItemModel } = require("@dbModels/workitems.model"); const { - convertRequestQueryToMongoQuery + QueryUpsDicomJsonFactory } = require("../../QIDO-RS/service/query-dicom-json-factory"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); @@ -28,16 +27,15 @@ class GetWorkItemService { } async getUps() { - let mongoQuery = (await convertRequestQueryToMongoQuery(this.query)).$match; - let queryOptions = { - query: mongoQuery, + query: this.query, skip: this.skip_, limit: this.limit_, requestParams: this.request.params }; + let queryFactory = new QueryUpsDicomJsonFactory(queryOptions); - let docs = await WorkItemModel.getDicomJson(queryOptions); + let docs = await queryFactory.getDicomJson(); return this.adjustDocs(docs); } From d2ed93c9234afad59f298d4202f14e9dc7acd735 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 13:43:19 +0800 Subject: [PATCH 336/365] fix: not append reason code when change state to canceled for work item - we use `supplementDiscontinuationReasonCode` function to change work item, but we are not store it in database - assign work item in update process to fix this problem --- .../controller/UPS-RS/service/change-workItem-state.service.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js index 23a41fb0..3e065759 100644 --- a/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js +++ b/api/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js @@ -53,6 +53,7 @@ class ChangeWorkItemStateService extends BaseWorkItemService { } let updatedWorkItem = await WorkItemModel.updateOneByUpsInstanceUID(this.request.params.workItem, { + ...this.workItemDicomJson.dicomJson, ...this.requestState.dicomJson }); From 7f90a44e41287102b6b7e31781eee72e08ed0702 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 14:05:01 +0800 Subject: [PATCH 337/365] refactor: wrap `subscribe` function in work item model --- .../controller/UPS-RS/service/subscribe.service.js | 7 ++----- models/mongodb/models/workitems.model.js | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js index 4e3b67e9..4eb16b1c 100644 --- a/api/dicom-web/controller/UPS-RS/service/subscribe.service.js +++ b/api/dicom-web/controller/UPS-RS/service/subscribe.service.js @@ -60,7 +60,8 @@ class SubscribeService extends BaseWorkItemService { async createOrUpdateSubscription(workItem) { let subscription = await this.findOneSubscription(); let subscribed = this.deletionLock ? SUBSCRIPTION_STATE.SUBSCRIBED_NO_LOCK : SUBSCRIPTION_STATE.SUBSCRIBED_LOCK; - await this.updateWorkItemSubscription(workItem, subscribed); + await workItem.subscribe(subscribed); + if (!subscription) { // Create return await UpsSubscriptionModel.createSubscriptionForWorkItem(workItem, this.subscriberAeTitle, this.deletionLock, subscribed); @@ -70,10 +71,6 @@ class SubscribeService extends BaseWorkItemService { } } - async updateWorkItemSubscription(workItem, subscription) { - workItem.subscribed = subscription; - await workItem.save(); - } //#endregion //#region Global Subscriptions diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index f4f9ef78..abf310e0 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -118,6 +118,10 @@ let workItemSchema = new mongoose.Schema( delete obj.subscribed; return obj; + }, + subscribe: async function (subscription) { + this.subscribed = subscription; + return await this.save(); } } } From fc82fe93ae54e031ce919c7b8f4660a56871952b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 14:20:32 +0800 Subject: [PATCH 338/365] refactor: separate `convertRequestQueryToMongoQuery` function into single file --- .../service/change-filtered-mwlItem-status.js | 2 +- .../MWL-RS/service/count-mwlItem.service.js | 2 +- .../MWL-RS/service/get-mwlItem.service.js | 2 +- .../service/query-dicom-json-factory.js | 133 +----------------- .../UPS-RS/service/base-workItem.service.js | 1 - dimse/queryBuilder.js | 2 +- models/mongodb/convertQuery.js | 133 ++++++++++++++++++ models/mongodb/models/workitems.model.js | 2 +- test/QIDO-RS-Service/common.test.js | 3 +- 9 files changed, 141 insertions(+), 139 deletions(-) create mode 100644 models/mongodb/convertQuery.js diff --git a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js index 8db68d70..cc020eef 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js @@ -2,7 +2,7 @@ const _ = require("lodash"); const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { DicomWebServiceError, DicomWebStatusCodes } = require("@error/dicom-web-service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); +const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); class ChangeFilteredMwlItemStatusService extends BaseQueryService { diff --git a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js index a2f338fa..8bfb1c49 100644 --- a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js @@ -1,6 +1,6 @@ const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); -const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); +const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); class GetMwlItemCountService extends BaseQueryService { constructor(req, res) { diff --git a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js index 7f8cc8cd..37c96fd9 100644 --- a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js @@ -1,6 +1,6 @@ const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); -const { convertRequestQueryToMongoQuery } = require("../../QIDO-RS/service/query-dicom-json-factory"); +const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); class GetMwlItemService extends BaseQueryService { constructor(req, res) { diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index 93e37f23..996b9db5 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -3,137 +3,7 @@ const { PatientModel } = require("@dbModels/patient.model"); const { StudyModel } = require("@dbModels/study.model"); const { SeriesModel } = require("@dbModels/series.model"); const { InstanceModel } = require("@dbModels/instance.model"); -const { dictionary } = require("@models/DICOM/dicom-tags-dic"); -const { mongoDateQuery, timeQuery } = require("@models/mongodb/service"); - - - -function checkIsOr(value, keyName) { - if (_.isObject(value) && _.get(value[keyName], "$or")) { - return true; - } - return false; -} - -/** - * convert value that contains comma to $or query of MongoDB - * @param {string} iKey - * @param {string} iValue - */ -function commaValue(iKey, iValue) { - let $or = []; - iValue = iValue.split(","); - for (let i = 0; i < iValue.length; i++) { - let obj = {}; - obj[iKey] = iValue[i]; - $or.push(obj); - } - return $or; -} - -/** - * - * @param {string} value - * @returns - */ -function getWildCardQuery(value) { - let wildCardIndex = value.indexOf("*"); - let questionIndex = value.indexOf("?"); - - if (wildCardIndex >= 0 || questionIndex >= 0) { - value = value.replace(/\*/gm, ".*"); - value = value.replace(/\?/gm, "."); - value = value.replace(/\^/gm, "\\^"); - value = "^" + value; - return new RegExp(value, "gm"); - } - - return value; -} - -/** - * TODO: separate this function to single file - * convert all request query object to to $or query and push to $and query - * @param {Object} iQuery - * @returns - */ -async function convertRequestQueryToMongoQuery(iQuery) { - let queryKey = Object.keys(iQuery); - let mongoQs = { - $match: { - $and: [] - } - }; - for (let i = 0; i < queryKey.length; i++) { - let mongoOrs = { - $or: [] - }; - let nowKey = queryKey[i]; - let value = commaValue(nowKey, iQuery[nowKey]); - for (let x = 0; x < value.length; x++) { - let nowValue = value[x][nowKey]; - value[x][nowKey] = getWildCardQuery(nowValue); - - try { - let keySplit = nowKey.split("."); - let tag = keySplit[keySplit.length - 2]; - let vrOfTag = dictionary.tagVR[tag]; - await vrQueryLookup[vrOfTag.vr](value[x], nowKey); - } catch (e) { - if (!(e instanceof TypeError)) console.error(e); - } - - if (checkIsOr(value[x], nowKey)) { - mongoOrs.$or.push(..._.get(value[x][nowKey], "$or")); - } else { - mongoOrs.$or.push(value[x]); - } - } - mongoQs.$match.$and.push(mongoOrs); - } - return mongoQs.$match.$and.length == 0 - ? { - $match: {} - } - : mongoQs; -} - -const vrQueryLookup = { - DA: async (value, tag) => { - let q = await mongoDateQuery(value, tag, false); - }, - DT: async (value, tag) => { - let q = await mongoDateQuery(value, tag, false, "YYYYMMDDhhmmss.SSSSSSZZ"); - }, - PN: async (value, tag) => { - let queryValue = _.cloneDeep(value[tag]); - value[tag] = { - $or: [ - { - [`${tag}.Alphabetic`]: queryValue - }, - { - [`${tag}.familyName`]: queryValue - }, - { - [`${tag}.givenName`]: queryValue - }, - { - [`${tag}.middleName`]: queryValue - }, - { - [`${tag}.prefix`]: queryValue - }, - { - [`${tag}.suffix`]: queryValue - } - ] - }; - }, - TM: async (value, tag) => { - value[tag] = timeQuery(value, tag); - } -}; +const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); /** * @@ -218,4 +88,3 @@ module.exports.QueryStudyDicomJsonFactory = QueryStudyDicomJsonFactory; module.exports.QuerySeriesDicomJsonFactory = QuerySeriesDicomJsonFactory; module.exports.QueryInstanceDicomJsonFactory = QueryInstanceDicomJsonFactory; module.exports.QueryUpsDicomJsonFactory = QueryUpsDicomJsonFactory; -module.exports.convertRequestQueryToMongoQuery = convertRequestQueryToMongoQuery; diff --git a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js index 2c8c020b..fac31bbc 100644 --- a/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/base-workItem.service.js @@ -2,7 +2,6 @@ const _ = require("lodash"); const { WorkItemEvent } = require("./workItem-event"); const { findWsArrayByAeTitle } = require("@root/websocket"); const { SUBSCRIPTION_STATE } = require("@models/DICOM/ups"); -const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); const { UpsGlobalSubscriptionModel } = require("@dbModels/upsGlobalSubscription"); const { UpsSubscriptionModel } = require("@dbModels/upsSubscription"); const { WorkItemModel } = require("@dbModels/workitems.model"); diff --git a/dimse/queryBuilder.js b/dimse/queryBuilder.js index 83a51663..df81e491 100644 --- a/dimse/queryBuilder.js +++ b/dimse/queryBuilder.js @@ -4,7 +4,7 @@ const { Attributes } = require("@dcm4che/data/Attributes"); const { queryTagsOfEachLevel } = require("./queryTagsOfEachLevel"); const { StringUtils } = require("@dcm4che/util/StringUtils"); const { intTagToString } = require("./utils"); -const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); +const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); const { default: Tag } = require("@dcm4che/data/Tag"); class DimseQueryBuilder { diff --git a/models/mongodb/convertQuery.js b/models/mongodb/convertQuery.js new file mode 100644 index 00000000..dc175754 --- /dev/null +++ b/models/mongodb/convertQuery.js @@ -0,0 +1,133 @@ +const _ = require("lodash"); + +const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { timeQuery, mongoDateQuery } = require("./service"); + +function checkIsOr(value, keyName) { + if (_.isObject(value) && _.get(value[keyName], "$or")) { + return true; + } + return false; +} + +/** + * convert value that contains comma to $or query of MongoDB + * @param {string} iKey + * @param {string} iValue + */ +function commaValue(iKey, iValue) { + let $or = []; + iValue = iValue.split(","); + for (let i = 0; i < iValue.length; i++) { + let obj = {}; + obj[iKey] = iValue[i]; + $or.push(obj); + } + return $or; +} + +/** + * + * @param {string} value + * @returns + */ +function getWildCardQuery(value) { + let wildCardIndex = value.indexOf("*"); + let questionIndex = value.indexOf("?"); + + if (wildCardIndex >= 0 || questionIndex >= 0) { + value = value.replace(/\*/gm, ".*"); + value = value.replace(/\?/gm, "."); + value = value.replace(/\^/gm, "\\^"); + value = "^" + value; + return new RegExp(value, "gm"); + } + + return value; +} + +/** + * + * convert all request query object to to $or query and push to $and query + * @param {Object} iQuery + * @returns + */ +async function convertRequestQueryToMongoQuery(iQuery) { + let queryKey = Object.keys(iQuery); + let mongoQs = { + $match: { + $and: [] + } + }; + for (let i = 0; i < queryKey.length; i++) { + let mongoOrs = { + $or: [] + }; + let nowKey = queryKey[i]; + let value = commaValue(nowKey, iQuery[nowKey]); + for (let x = 0; x < value.length; x++) { + let nowValue = value[x][nowKey]; + value[x][nowKey] = getWildCardQuery(nowValue); + + try { + let keySplit = nowKey.split("."); + let tag = keySplit[keySplit.length - 2]; + let vrOfTag = dictionary.tagVR[tag]; + await vrQueryLookup[vrOfTag.vr](value[x], nowKey); + } catch (e) { + if (!(e instanceof TypeError)) console.error(e); + } + + if (checkIsOr(value[x], nowKey)) { + mongoOrs.$or.push(..._.get(value[x][nowKey], "$or")); + } else { + mongoOrs.$or.push(value[x]); + } + } + mongoQs.$match.$and.push(mongoOrs); + } + return mongoQs.$match.$and.length == 0 + ? { + $match: {} + } + : mongoQs; +} + +const vrQueryLookup = { + DA: async (value, tag) => { + let q = await mongoDateQuery(value, tag, false); + }, + DT: async (value, tag) => { + let q = await mongoDateQuery(value, tag, false, "YYYYMMDDhhmmss.SSSSSSZZ"); + }, + PN: async (value, tag) => { + let queryValue = _.cloneDeep(value[tag]); + value[tag] = { + $or: [ + { + [`${tag}.Alphabetic`]: queryValue + }, + { + [`${tag}.familyName`]: queryValue + }, + { + [`${tag}.givenName`]: queryValue + }, + { + [`${tag}.middleName`]: queryValue + }, + { + [`${tag}.prefix`]: queryValue + }, + { + [`${tag}.suffix`]: queryValue + } + ] + }; + }, + TM: async (value, tag) => { + value[tag] = timeQuery(value, tag); + } +}; + +module.exports.convertRequestQueryToMongoQuery = convertRequestQueryToMongoQuery; \ No newline at end of file diff --git a/models/mongodb/models/workitems.model.js b/models/mongodb/models/workitems.model.js index abf310e0..30fbd68f 100644 --- a/models/mongodb/models/workitems.model.js +++ b/models/mongodb/models/workitems.model.js @@ -6,7 +6,7 @@ const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { SUBSCRIPTION_STATE } = require("../../DICOM/ups"); const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); const { PatientModel } = require("./patient.model"); -const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); +const { convertRequestQueryToMongoQuery } = require("../convertQuery"); let workItemSchema = new mongoose.Schema( { diff --git a/test/QIDO-RS-Service/common.test.js b/test/QIDO-RS-Service/common.test.js index 52604525..8462de7c 100644 --- a/test/QIDO-RS-Service/common.test.js +++ b/test/QIDO-RS-Service/common.test.js @@ -3,8 +3,9 @@ const patientModel = require("../../models/mongodb/models/patient.model"); const { DicomJsonModel } = require("../../models/DICOM/dicom-json-model"); const { expect } = require("chai"); const _ = require("lodash"); -const { convertRequestQueryToMongoQuery } = require("../../api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); + const moment = require("moment"); +const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.service"); From 652c26ee12398843feca9e66a317c9adffa92516 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 15:04:00 +0800 Subject: [PATCH 339/365] refactor: use `createOrUpdatePatient` instead directly call mongoose method --- .../PAM-RS/service/update-patient.service.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/api/dicom-web/controller/PAM-RS/service/update-patient.service.js b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js index fdaa1f85..c117589d 100644 --- a/api/dicom-web/controller/PAM-RS/service/update-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/update-patient.service.js @@ -15,18 +15,9 @@ class UpdatePatientService { async update() { let { patientID } = this.request.params; - return await PatientModel.findOneAndUpdate({ - patientID - }, { - $set: { - ...this.incomingPatient - } - }, { - upsert: true, - new: true - }); + return await PatientModel.createOrUpdatePatient(patientID, { ...this.incomingPatient }); } - + #adjustIncomingPatient() { set(this.incomingPatient, "00100020.Value", [this.request.params.patientID]); set(this.incomingPatient, "patientID", this.request.params.patientID); From 5ec0336e602c88aaee52020984cda5c59602dfde Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 15:20:43 +0800 Subject: [PATCH 340/365] refactor: wrap `getCountByPatientID` in patient model --- models/mongodb/models/patient.model.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/models/mongodb/models/patient.model.js b/models/mongodb/models/patient.model.js index 807a882a..9905beb4 100644 --- a/models/mongodb/models/patient.model.js +++ b/models/mongodb/models/patient.model.js @@ -79,6 +79,15 @@ let patientSchemaOptions = _.merge( return await mongoose.model("patient").findOneAndUpdate({ patientID }, patient, { upsert: true, new: true }); + }, + /** + * + * @param {string} patientID + */ + getCountByPatientID: async function(patientID) { + return await mongoose.model("patient").countDocuments({ + patientID + }); } } } From e6eebbf5cdaca238e94b108bde5c179aeb3b7bc1 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 15:23:34 +0800 Subject: [PATCH 341/365] refactor: wrap find/create/update methods for `MwlItemModel` --- .../MWL-RS/service/create-mwlItem.service.js | 27 +++----------- models/mongodb/models/mwlitems.model.js | 37 ++++++++++++++++--- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js index a4fa2417..f9f505b3 100644 --- a/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/create-mwlItem.service.js @@ -88,9 +88,7 @@ class CreateMwlItemService { async checkPatientExist() { let patientID = this.requestMwlItemDicomJson.getString("00100020"); - let patientCount = await PatientModel.countDocuments({ - patientID - }); + let patientCount = await PatientModel.getCountByPatientID(patientID); if (patientCount <= 0) { throw new DicomWebServiceError( DicomWebStatusCodes.MissingAttribute, @@ -108,31 +106,18 @@ class CreateMwlItemService { let studyInstanceUID = mwlDicomJson.getValue(dictionary.keyword.StudyInstanceUID); let spsItem = new BaseDicomJson(mwlDicomJson.getValue(dictionary.keyword.ScheduledProcedureStepSequence)); let spsID = spsItem.getValue(dictionary.keyword.ScheduledProcedureStepID); - let foundMwl = await MwlItemModel.findOne({ - $and: [ - { - "0020000D.Value.0": studyInstanceUID - }, - { - "00400100.Value.0.00400009.Value.0": spsID - } - ] - }); + let foundMwl = await MwlItemModel.findOneByStudyInstanceUIDAndSpsID(studyInstanceUID, spsID); if (!foundMwl) { // create - let mwlItemModelObj = new MwlItemModel(mwlDicomJson.dicomJson); - await mwlItemModelObj.save(); + let createdMwlItem = await MwlItemModel.createWithGeneralDicomJson(mwlDicomJson.dicomJson); this.apiLogger.logger.info(`create mwl item: ${studyInstanceUID}`); - return mwlItemModelObj.toGeneralDicomJson(); + return createdMwlItem.toGeneralDicomJson(); } else { // update - foundMwl.$set({ - ...mwlDicomJson.dicomJson - }); - await foundMwl.save(); + let updatedMwlItem = await MwlItemModel.updateOneWithGeneralDicomJson(foundMwl, mwlDicomJson.dicomJson); this.apiLogger.logger.info(`update mwl item: ${studyInstanceUID}`); - return foundMwl.toGeneralDicomJson(); + return updatedMwlItem.toGeneralDicomJson(); } } diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index be7fe8d8..67efa4dc 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -46,7 +46,7 @@ let mwlItemSchema = new mongoose.Schema( }) .exec(); - + let mwlDicomJson = docs.map((v) => { let obj = v.toObject(); delete obj._id; @@ -67,8 +67,8 @@ let mwlItemSchema = new mongoose.Schema( getCount: async function (query) { return await mongoose.model("mwlItems").countDocuments(query); }, - deleteByStudyInstanceUIDAndSpsID: async function(studyUID, spsID) { - return await mongoose.model("mwlItems").deleteMany({ + deleteByStudyInstanceUIDAndSpsID: async function (studyUID, spsID) { + return await mongoose.model("mwlItems").deleteMany({ $and: [ { [`${dictionary.keyword.StudyInstanceUID}.Value.0`]: studyUID @@ -77,11 +77,38 @@ let mwlItemSchema = new mongoose.Schema( [`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepID}.Value.0`]: spsID } ] - }); + }); + }, + /** + * + * @param {string} studyUID + * @param {string} spsID + */ + findOneByStudyInstanceUIDAndSpsID: async function (studyUID, spsID) { + return await mongoose.model("mwlItems").findOne({ + $and: [ + { + [`${dictionary.keyword.StudyInstanceUID}.Value.0`]: studyUID + }, + { + [`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepID}.Value.0`]: spsID + } + ] + }); + }, + createWithGeneralDicomJson: async function (generalDicomJson) { + let mwlItemModelObj = new mongoose.model("mwlItems")(generalDicomJson); + return await mwlItemModelObj.save(); + }, + updateOneWithGeneralDicomJson: async function (mwlItem, generalDicomJson) { + mwlItem.$set({ + ...generalDicomJson + }); + return await mwlItem.save(); } }, methods: { - toGeneralDicomJson: async function() { + toGeneralDicomJson: async function () { let obj = this.toObject(); delete obj._id; delete obj.id; From be405cd15661981de2b78286d7cc702b3835b2c3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 18:52:17 +0800 Subject: [PATCH 342/365] chore: add dependency-cruiser config --- .dependency-cruiser.js | 476 +++++++++++++++++++++++++++++++++++++++++ package-lock.json | 24 +-- 2 files changed, 488 insertions(+), 12 deletions(-) create mode 100644 .dependency-cruiser.js diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js new file mode 100644 index 00000000..f9a34107 --- /dev/null +++ b/.dependency-cruiser.js @@ -0,0 +1,476 @@ +/** @type {import('dependency-cruiser').IConfiguration} */ +module.exports = { + forbidden: [ + { + name: 'no-circular', + severity: 'warn', + comment: + 'This dependency is part of a circular relationship. You might want to revise ' + + 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + from: {}, + to: { + circular: true + } + }, + { + name: 'no-orphans', + comment: + "This is an orphan module - it's likely not used (anymore?). Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + "add an exception for it in your dependency-cruiser configuration. By default " + + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", + severity: 'warn', + from: { + orphan: true, + pathNot: [ + '(^|/)[.][^/]+[.](js|cjs|mjs|ts|json)$', // dot files + '[.]d[.]ts$', // TypeScript declaration files + '(^|/)tsconfig[.]json$', // TypeScript config + '(^|/)(babel|webpack)[.]config[.](js|cjs|mjs|ts|json)$' // other configs + ] + }, + to: {}, + }, + { + name: 'no-deprecated-core', + comment: + 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "bound to exist - node doesn't deprecate lightly.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'core' + ], + path: [ + '^(v8/tools/codemap)$', + '^(v8/tools/consarray)$', + '^(v8/tools/csvparser)$', + '^(v8/tools/logreader)$', + '^(v8/tools/profile_view)$', + '^(v8/tools/profile)$', + '^(v8/tools/SourceMap)$', + '^(v8/tools/splaytree)$', + '^(v8/tools/tickprocessor-driver)$', + '^(v8/tools/tickprocessor)$', + '^(node-inspect/lib/_inspect)$', + '^(node-inspect/lib/internal/inspect_client)$', + '^(node-inspect/lib/internal/inspect_repl)$', + '^(async_hooks)$', + '^(punycode)$', + '^(domain)$', + '^(constants)$', + '^(sys)$', + '^(_linklist)$', + '^(_stream_wrap)$' + ], + } + }, + { + name: 'not-to-deprecated', + comment: + 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + + 'version of that module, or find an alternative. Deprecated modules are a security risk.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'deprecated' + ] + } + }, + { + name: 'no-non-package-json', + severity: 'error', + comment: + "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " + + "in your package.json.", + from: {}, + to: { + dependencyTypes: [ + 'npm-no-pkg', + 'npm-unknown' + ] + } + }, + { + name: 'not-to-unresolvable', + comment: + "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + + 'module: add it to your package.json. In all other cases you likely already know what to do.', + severity: 'error', + from: {}, + to: { + couldNotResolve: true + } + }, + { + name: 'no-duplicate-dep-types', + comment: + "Likely this module depends on an external ('npm') package that occurs more than once " + + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + + "maintenance problems later on.", + severity: 'warn', + from: {}, + to: { + moreThanOneDependencyType: true, + // as it's pretty common to have a type import be a type only import + // _and_ (e.g.) a devDependency - don't consider type-only dependency + // types for this rule + dependencyTypesNot: ["type-only"] + } + }, + + /* rules you might want to tweak for your specific situation: */ + + { + name: 'not-to-spec', + comment: + 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', + severity: 'error', + from: {}, + to: { + path: '[.](spec|test)[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$' + } + }, + { + name: 'not-to-dev-dep', + severity: 'error', + comment: + "This module depends on an npm package from the 'devDependencies' section of your " + + 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + 'section of your package.json. If this module is development only - add it to the ' + + 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + from: { + path: '^(\.)', + pathNot: '[.](spec|test)[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$' + }, + to: { + dependencyTypes: [ + 'npm-dev', + ], + // type only dependencies are not a problem as they don't end up in the + // production code or are ignored by the runtime. + dependencyTypesNot: [ + 'type-only' + ], + pathNot: [ + 'node_modules/@types/' + ] + } + }, + { + name: 'optional-deps-used', + severity: 'info', + comment: + "This module depends on an npm package that is declared as an optional dependency " + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + "dependency-cruiser configuration.", + from: {}, + to: { + dependencyTypes: [ + 'npm-optional' + ] + } + }, + { + name: 'peer-deps-used', + comment: + "This module depends on an npm package that is declared as a peer dependency " + + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + + "other cases - maybe not so much. If the use of a peer dependency is intentional " + + "add an exception to your dependency-cruiser configuration.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'npm-peer' + ] + } + } + ], + options: { + + /* conditions specifying which files not to follow further when encountered: + - path: a regular expression to match + - dependencyTypes: see https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md#dependencytypes-and-dependencytypesnot + for a complete list + */ + doNotFollow: { + path: 'node_modules' + }, + + /* conditions specifying which dependencies to exclude + - path: a regular expression to match + - dynamic: a boolean indicating whether to ignore dynamic (true) or static (false) dependencies. + leave out if you want to exclude neither (recommended!) + */ + exclude : { + path: "(wrapper|docs)", + dynamic: true + }, + + /* pattern specifying which files to include (regular expression) + dependency-cruiser will skip everything not matching this pattern + */ + // includeOnly : '', + + /* dependency-cruiser will include modules matching against the focus + regular expression in its output, as well as their neighbours (direct + dependencies and dependents) + */ + // focus : '', + + /* List of module systems to cruise. + When left out dependency-cruiser will fall back to the list of _all_ + module systems it knows of. It's the default because it's the safe option + It might come at a performance penalty, though. + moduleSystems: ['amd', 'cjs', 'es6', 'tsd'] + + As in practice only commonjs ('cjs') and ecmascript modules ('es6') + are widely used, you can limit the moduleSystems to those. + */ + + // moduleSystems: ['cjs', 'es6'], + + /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/develop/' + to open it on your online repo or `vscode://file/${process.cwd()}/` to + open it in visual studio code), + */ + // prefix: '', + + /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation + true: also detect dependencies that only exist before typescript-to-javascript compilation + "specify": for each dependency identify whether it only exists before compilation or also after + */ + // tsPreCompilationDeps: false, + + /* + list of extensions to scan that aren't javascript or compile-to-javascript. + Empty by default. Only put extensions in here that you want to take into + account that are _not_ parsable. + */ + // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"], + + /* if true combines the package.jsons found from the module up to the base + folder the cruise is initiated from. Useful for how (some) mono-repos + manage dependencies & dependency definitions. + */ + // combinedDependencies: false, + + /* if true leave symlinks untouched, otherwise use the realpath */ + // preserveSymlinks: false, + + /* TypeScript project file ('tsconfig.json') to use for + (1) compilation and + (2) resolution (e.g. with the paths property) + + The (optional) fileName attribute specifies which file to take (relative to + dependency-cruiser's current working directory). When not provided + defaults to './tsconfig.json'. + */ + tsConfig: { + fileName: 'jsconfig.json' + }, + + /* Webpack configuration to use to get resolve options from. + + The (optional) fileName attribute specifies which file to take (relative + to dependency-cruiser's current working directory. When not provided defaults + to './webpack.conf.js'. + + The (optional) `env` and `arguments` attributes contain the parameters to be passed if + your webpack config is a function and takes them (see webpack documentation + for details) + */ + // webpackConfig: { + // fileName: 'webpack.config.js', + // env: {}, + // arguments: {} + // }, + + /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use + for compilation (and whatever other naughty things babel plugins do to + source code). This feature is well tested and usable, but might change + behavior a bit over time (e.g. more precise results for used module + systems) without dependency-cruiser getting a major version bump. + */ + // babelConfig: { + // fileName: '.babelrc', + // }, + + /* List of strings you have in use in addition to cjs/ es6 requires + & imports to declare module dependencies. Use this e.g. if you've + re-declared require, use a require-wrapper or use window.require as + a hack. + */ + // exoticRequireStrings: [], + /* options to pass on to enhanced-resolve, the package dependency-cruiser + uses to resolve module references to disk. You can set most of these + options in a webpack.conf.js - this section is here for those + projects that don't have a separate webpack config file. + + Note: settings in webpack.conf.js override the ones specified here. + */ + enhancedResolveOptions: { + /* List of strings to consider as 'exports' fields in package.json. Use + ['exports'] when you use packages that use such a field and your environment + supports it (e.g. node ^12.19 || >=14.7 or recent versions of webpack). + + If you have an `exportsFields` attribute in your webpack config, that one + will have precedence over the one specified here. + */ + exportsFields: ["exports"], + /* List of conditions to check for in the exports field. e.g. use ['imports'] + if you're only interested in exposed es6 modules, ['require'] for commonjs, + or all conditions at once `(['import', 'require', 'node', 'default']`) + if anything goes for you. Only works when the 'exportsFields' array is + non-empty. + + If you have a 'conditionNames' attribute in your webpack config, that one will + have precedence over the one specified here. + */ + conditionNames: ["import", "require", "node", "default"], + /* + The extensions, by default are the same as the ones dependency-cruiser + can access (run `npx depcruise --info` to see which ones that are in + _your_ environment. If that list is larger than what you need (e.g. + it contains .js, .jsx, .ts, .tsx, .cts, .mts - but you don't use + TypeScript you can pass just the extensions you actually use (e.g. + [".js", ".jsx"]). This can speed up the most expensive step in + dependency cruising (module resolution) quite a bit. + */ + // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + /* + If your TypeScript project makes use of types specified in 'types' + fields in package.jsons of external dependencies, specify "types" + in addition to "main" in here, so enhanced-resolve (the resolver + dependency-cruiser uses) knows to also look there. You can also do + this if you're not sure, but still use TypeScript. In a future version + of dependency-cruiser this will likely become the default. + */ + mainFields: ["main", "types", "typings"], + /* + A list of alias fields in manifests (package.jsons). + Specify a field, such as browser, to be parsed according to + [this specification](https://github.com/defunctzombie/package-browser-field-spec). + Also see [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields) + in the webpack docs. + + Defaults to an empty array (don't use any alias fields). + */ + // aliasFields: ["browser"], + }, + reporterOptions: { + dot: { + /* pattern of modules that can be consolidated in the detailed + graphical dependency graph. The default pattern in this configuration + collapses everything in node_modules to one folder deep so you see + the external modules, but not the innards your app depends upon. + */ + collapsePattern: 'node_modules/(@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + don't worry - dependency-cruiser will fall back to the default one. + */ + // theme: { + // graph: { + // /* use splines: "ortho" for straight lines. Be aware though + // graphviz might take a long time calculating ortho(gonal) + // routings. + // */ + // splines: "true" + // }, + // modules: [ + // { + // criteria: { matchesFocus: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesFocus: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { matchesReaches: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesReaches: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { source: "^src/model" }, + // attributes: { fillcolor: "#ccccff" } + // }, + // { + // criteria: { source: "^src/view" }, + // attributes: { fillcolor: "#ccffcc" } + // }, + // ], + // dependencies: [ + // { + // criteria: { "rules[0].severity": "error" }, + // attributes: { fontcolor: "red", color: "red" } + // }, + // { + // criteria: { "rules[0].severity": "warn" }, + // attributes: { fontcolor: "orange", color: "orange" } + // }, + // { + // criteria: { "rules[0].severity": "info" }, + // attributes: { fontcolor: "blue", color: "blue" } + // }, + // { + // criteria: { resolved: "^src/model" }, + // attributes: { color: "#0000ff77" } + // }, + // { + // criteria: { resolved: "^src/view" }, + // attributes: { color: "#00770077" } + // } + // ] + // } + }, + archi: { + /* pattern of modules that can be consolidated in the high level + graphical dependency graph. If you use the high level graphical + dependency graph reporter (`archi`) you probably want to tweak + this collapsePattern to your situation. + */ + collapsePattern: '^(packages|src|lib|app|bin|test(s?)|spec(s?))/[^/]+|node_modules/(@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + for 'archi' dependency-cruiser will use the one specified in the + dot section (see above), if any, and otherwise use the default one. + */ + // theme: { + // }, + }, + "text": { + "highlightFocused": true + }, + } + } +}; +// generated: dependency-cruiser@16.0.0 on 2024-01-19T10:44:28.622Z diff --git a/package-lock.json b/package-lock.json index 800ba978..d5a63126 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1594,9 +1594,9 @@ } }, "node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "peer": true, "bin": { @@ -4262,9 +4262,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "peer": true, "engines": { @@ -9569,9 +9569,9 @@ } }, "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "peer": true }, @@ -11604,9 +11604,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "peer": true }, From a9c8e2f6924935ba1c3ae588a35a7d22f2f10502 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 19:19:55 +0800 Subject: [PATCH 343/365] fix: circular in `dicom-json-model` - Remove unused `PatientModel` --- models/DICOM/dicom-json-model.js | 1 - 1 file changed, 1 deletion(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 5c9136ac..87c8cd18 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -11,7 +11,6 @@ const flat = require("flat"); const shortHash = require("shorthash2"); const dicomBulkDataModel = require("@dbModels/dicomBulkData.model"); const { logger } = require("../../utils/logs/log"); -const { PatientModel } = require("@dbModels/patient.model"); const { tagsNeedStore } = require("./dicom-tags-mapping"); const { raccoonConfig } = require("../../config-class"); From 535de78ad00a8a3623668cc46a9c45d1dd70f3ee Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 19:35:26 +0800 Subject: [PATCH 344/365] docs: move `get-swagger.js` to docs folder --- get-swagger.js => docs/get-swagger.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) rename get-swagger.js => docs/get-swagger.js (68%) diff --git a/get-swagger.js b/docs/get-swagger.js similarity index 68% rename from get-swagger.js rename to docs/get-swagger.js index 03478058..f6050900 100644 --- a/get-swagger.js +++ b/docs/get-swagger.js @@ -1,3 +1,4 @@ +const path = require("path"); const swaggerJsDoc = require("swagger-jsdoc"); const fsP = require("fs").promises; @@ -19,14 +20,17 @@ const fsP = require("fs").promises; // Path to the API docs // Note that this path is relative to the current directory from which the Node.js is ran, not the application itself. apis: [ - `${__dirname}/api/**/*.js`, - `${__dirname}/docs/swagger/parameters/*.yaml`, - `${__dirname}/docs/swagger/schemas/*.yaml`, - `${__dirname}/docs/swagger/responses/*.yaml` + `${__dirname}/../api/**/*.js`, + `${__dirname}/../docs/swagger/parameters/*.yaml`, + `${__dirname}/../docs/swagger/schemas/*.yaml`, + `${__dirname}/../docs/swagger/responses/*.yaml` ] }; const swaggerSpec = await swaggerJsDoc(options); console.log(JSON.stringify(swaggerSpec, null, 4)); - await fsP.writeFile("docs/swagger/openapi.json", JSON.stringify(swaggerSpec, null, 4)); + await fsP.writeFile( + path.join(__dirname, "./swagger/openapi.json"), + JSON.stringify(swaggerSpec, null, 4) + ); })(); From d2ef570d5d9f9bfce75d162afccde63d589aab2f Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 19:35:56 +0800 Subject: [PATCH 345/365] chore: update `not-to-dev-dep` pathNot, to prevent detect test and docs folder --- .dependency-cruiser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js index f9a34107..2f32d16b 100644 --- a/.dependency-cruiser.js +++ b/.dependency-cruiser.js @@ -135,7 +135,7 @@ module.exports = { severity: 'error', from: {}, to: { - path: '[.](spec|test)[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$' + path: '^(test|spec)\/.*[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$' } }, { @@ -149,7 +149,7 @@ module.exports = { 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', from: { path: '^(\.)', - pathNot: '[.](spec|test)[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$' + pathNot: '^(test|spec|docs)\/.*[.](js|mjs|cjs|ts|ls|coffee|litcoffee|coffee[.]md)$' }, to: { dependencyTypes: [ From f5a9070545973160c97d3c0e4883172b91927586 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 19:51:18 +0800 Subject: [PATCH 346/365] refactor: remove duplicate code, use `toGeneralDicomJson` instead --- models/mongodb/models/mwlitems.model.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 67efa4dc..126f9c96 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -47,12 +47,7 @@ let mwlItemSchema = new mongoose.Schema( .exec(); - let mwlDicomJson = docs.map((v) => { - let obj = v.toObject(); - delete obj._id; - delete obj.id; - return obj; - }); + let mwlDicomJson = await Promise.all(docs.map(async (v) => await v.toGeneralDicomJson())); return mwlDicomJson; From 34dcc78986bb5a0d0bcf9202663e830055e39c97 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 19:51:56 +0800 Subject: [PATCH 347/365] refactor: add `QueryMwlDicomJsonFactory` for get mwl item service --- .../controller/MWL-RS/service/get-mwlItem.service.js | 8 ++++---- .../QIDO-RS/service/query-dicom-json-factory.js | 12 +++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js index 37c96fd9..81446a19 100644 --- a/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js @@ -1,6 +1,6 @@ const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); -const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); +const { QueryMwlDicomJsonFactory } = require("@api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); class GetMwlItemService extends BaseQueryService { constructor(req, res) { @@ -8,15 +8,15 @@ class GetMwlItemService extends BaseQueryService { } async getMwlItems() { - let query = (await convertRequestQueryToMongoQuery(this.query)).$match; let queryOptions = { - query, + query: this.query, skip: this.skip_, limit: this.limit_, includeFields: this.includeFields_ }; - let docs = await MwlItemModel.getDicomJson(queryOptions); + let queryFactory = new QueryMwlDicomJsonFactory(queryOptions); + let docs = await queryFactory.getDicomJson(); return docs; } diff --git a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js index 996b9db5..5f8da6ec 100644 --- a/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js +++ b/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory.js @@ -3,7 +3,9 @@ const { PatientModel } = require("@dbModels/patient.model"); const { StudyModel } = require("@dbModels/study.model"); const { SeriesModel } = require("@dbModels/series.model"); const { InstanceModel } = require("@dbModels/instance.model"); +const { WorkItemModel } = require("@dbModels/workitems.model"); const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); /** * @@ -78,7 +80,14 @@ class QueryInstanceDicomJsonFactory extends QueryDicomJsonFactory { class QueryUpsDicomJsonFactory extends QueryDicomJsonFactory { constructor(queryOptions) { super(queryOptions); - this.model = require("@dbModels/workitems.model").WorkItemModel; + this.model = WorkItemModel; + } +} + +class QueryMwlDicomJsonFactory extends QueryDicomJsonFactory { + constructor(queryOptions) { + super(queryOptions); + this.model = MwlItemModel; } } @@ -88,3 +97,4 @@ module.exports.QueryStudyDicomJsonFactory = QueryStudyDicomJsonFactory; module.exports.QuerySeriesDicomJsonFactory = QuerySeriesDicomJsonFactory; module.exports.QueryInstanceDicomJsonFactory = QueryInstanceDicomJsonFactory; module.exports.QueryUpsDicomJsonFactory = QueryUpsDicomJsonFactory; +module.exports.QueryMwlDicomJsonFactory = QueryMwlDicomJsonFactory; From 3807738a71f038ed1d97959e661c4cc96527cf64 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 20:07:46 +0800 Subject: [PATCH 348/365] refactor: move `convertRequestQueryToMongoQuery` to `getCount` --- .../controller/MWL-RS/service/count-mwlItem.service.js | 2 -- models/mongodb/models/mwlitems.model.js | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js index 8bfb1c49..23175b0c 100644 --- a/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js +++ b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js @@ -1,6 +1,5 @@ const { MwlItemModel } = require("@dbModels/mwlitems.model"); const { BaseQueryService } = require("@root/api/dicom-web/service/base-query.service"); -const { convertRequestQueryToMongoQuery } = require("@models/mongodb/convertQuery"); class GetMwlItemCountService extends BaseQueryService { constructor(req, res) { @@ -8,7 +7,6 @@ class GetMwlItemCountService extends BaseQueryService { } async getMwlItemCount() { - this.query = (await convertRequestQueryToMongoQuery(this.query)).$match; return await MwlItemModel.getCount(this.query); } } diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 126f9c96..27e7a000 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -7,6 +7,7 @@ const { IncludeFieldsFactory } = require("../service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); const { raccoonConfig } = require("@root/config-class"); const { BaseDicomJson } = require("@models/DICOM/dicom-json-model"); +const { convertRequestQueryToMongoQuery } = require("../convertQuery"); let Common; if (raccoonConfig.dicomDimseConfig.enableDimse) { @@ -60,7 +61,8 @@ let mwlItemSchema = new mongoose.Schema( return includeFieldsFactory.getMwlLevelFields(); }, getCount: async function (query) { - return await mongoose.model("mwlItems").countDocuments(query); + let mongoQuery = await convertRequestQueryToMongoQuery(query); + return await mongoose.model("mwlItems").countDocuments(mongoQuery); }, deleteByStudyInstanceUIDAndSpsID: async function (studyUID, spsID) { return await mongoose.model("mwlItems").deleteMany({ From 094e8a3c41c39f7cf0a2057ab90fe4a2385c1193 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 20:22:02 +0800 Subject: [PATCH 349/365] refactor: use `findOneByStudyInstanceUIDAndSpsID` instead `findOne` --- .../MWL-RS/service/change-mwlItem-status.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js index 051ae76b..867d3d2d 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js @@ -25,16 +25,7 @@ class ChangeMwlItemStatusService { } async getMwlItemByStudyUIDAndSpsID() { - return await MwlItemModel.findOne({ - $and: [ - { - "00400100.Value.0.00400009.Value.0": this.request.params.spsID - }, - { - "0020000D.Value.0": this.request.params.studyUID - } - ] - }); + return await MwlItemModel.findOneByStudyInstanceUIDAndSpsID(this.request.params.studyUID, this.request.params.spsID); } } From b519dd3535f2ab567b24d8e2f04038405d3dbbc7 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 20:22:16 +0800 Subject: [PATCH 350/365] refactor: wrap `updateStatus` for mwl work item instance --- .../controller/MWL-RS/service/change-mwlItem-status.js | 3 +-- models/mongodb/models/mwlitems.model.js | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js index 867d3d2d..8cc13739 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-mwlItem-status.js @@ -18,8 +18,7 @@ class ChangeMwlItemStatusService { throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "No such object instance", 404); } - _.set(mwlItem, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); - await mwlItem.save(); + await mwlItem.updateStatus(status); return mwlItem.toGeneralDicomJson(); } diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 27e7a000..20c04cc1 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -117,6 +117,14 @@ let mwlItemSchema = new mongoose.Schema( getAttributes: async function () { let jsonStr = JSON.stringify(this.toDicomJson()); return await Common.getAttributesFromJsonString(jsonStr); + }, + /** + * + * @param {"SCHEDULED" | "ARRIVED" | "READY" | "STARTED" | "DEPARTED" | "CANCELED" | "DISCONTINUED" | "COMPLETED"} status + */ + updateStatus: async function (status) { + this.$set(`${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); + await this.save(); } } } From 094b637519ac18199d6bd390aa7689627c79bd35 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Fri, 19 Jan 2024 21:07:00 +0800 Subject: [PATCH 351/365] feat: wrap `findMwlItems` in mwl item model --- .../MWL-RS/service/change-filtered-mwlItem-status.js | 3 +-- models/mongodb/models/mwlitems.model.js | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js index cc020eef..6ff7bcfe 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js @@ -26,8 +26,7 @@ class ChangeFilteredMwlItemStatusService extends BaseQueryService { } async getMwlItems() { - let query = (await convertRequestQueryToMongoQuery(this.query)).$match; - return await MwlItemModel.find(query); + return await MwlItemModel.findMwlItems(this.query); } } diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 20c04cc1..9d53e3d5 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -102,6 +102,10 @@ let mwlItemSchema = new mongoose.Schema( ...generalDicomJson }); return await mwlItem.save(); + }, + findMwlItems: async function(query) { + let mongoQuery = await convertRequestQueryToMongoQuery(query); + return await mongoose.model("mwlItems").find(mongoQuery); } }, methods: { From 071ad0ef0a289d0dcda61a85b4f7337e15f7170b Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 20 Jan 2024 14:07:27 +0800 Subject: [PATCH 352/365] fix: should use `originUrl` in `postProcess` --- api/controller.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controller.class.js b/api/controller.class.js index 837ecdfa..e8a7253c 100644 --- a/api/controller.class.js +++ b/api/controller.class.js @@ -33,7 +33,7 @@ class Controller { async mainProcess() { } async postProcess() { - let currentRouterPlugin = pluginGroup.findLocalPlugin(this.request.url, this.request.method) || new LocalPlugin(); + let currentRouterPlugin = pluginGroup.findLocalPlugin(this.request.originalUrl, this.request.method) || new LocalPlugin(); try { From d3c0045fde47fb8376c538fe7f0baf94cfecf122 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 20 Jan 2024 14:20:18 +0800 Subject: [PATCH 353/365] refactor: move `syncFhir` to plugin - The sync fhir is part of the plugin and is not within the scope of dicom - so we move it to plugin folder --- .../STOW-RS/service/stow-rs.service.js | 36 ++++++++----------- .../controller/STOW-RS/storeInstance.js | 2 +- config-class.js | 8 ----- plugins/config.template.js | 15 ++++++++ .../syncToFhirServer}/DICOMToFHIR.js | 8 ++--- .../syncToFhirServer}/dicom-fhir.service.js | 29 ++++++++++----- plugins/syncToFhirServer/index.js | 30 ++++++++++++++++ .../syncToFhirServer/mongoose}/syncFHIRLog.js | 0 plugins/syncToFhirServer/storeInfo.d.ts | 7 ++++ 9 files changed, 92 insertions(+), 43 deletions(-) rename {models/FHIR/DICOM => plugins/syncToFhirServer}/DICOMToFHIR.js (98%) rename {api/dicom-web/controller/STOW-RS/service => plugins/syncToFhirServer}/dicom-fhir.service.js (78%) create mode 100644 plugins/syncToFhirServer/index.js rename {models/mongodb/models => plugins/syncToFhirServer/mongoose}/syncFHIRLog.js (100%) create mode 100644 plugins/syncToFhirServer/storeInfo.d.ts diff --git a/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js b/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js index e9b9cae5..83bbc104 100644 --- a/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js +++ b/api/dicom-web/controller/STOW-RS/service/stow-rs.service.js @@ -6,23 +6,13 @@ const { DicomJsonBinaryDataModel } = require("@dicom-json-model"); const { DicomFileSaver } = require("./dicom-file-saver"); -const { DicomFhirService } = require("./dicom-fhir.service"); const { DicomJpegGenerator } = require("./dicom-jpeg-generator"); const { logger } = require("../../../../../utils/logs/log"); - -const { raccoonConfig } = require("../../../../../config-class"); const { DicomWebService } = require("../../../service/dicom-web.service"); const { AuditManager } = require("@models/DICOM/audit/auditManager"); const { EventType } = require("@models/DICOM/audit/eventType"); const { EventOutcomeIndicator } = require("@models/DICOM/audit/auditUtils"); -const { - apiPath: DICOM_WEB_API_PATH -} = raccoonConfig.dicomWebConfig; - -const { - isSyncToFhir -} = raccoonConfig.fhirConfig; const StowRsFailureCode = { "GENERAL_FAILURE": "272", @@ -34,11 +24,19 @@ const StowRsFailureCode = { class StowRsService { /** * @param {import('express').Request} req + * @param {import('express').Response} res * @param {import('formidable').File[]} uploadFiles */ - constructor(req, uploadFiles) { + constructor(req, res, uploadFiles) { this.request = req; + this.response = res; + + this.response.locals = { + "storeInfos": [] + }; + this.uploadFiles = uploadFiles; + this.responseMessage = { "00081190": { //Study retrieve URL @@ -90,14 +88,18 @@ class StowRsService { let storeInstanceResult = await this.storeInstance(currentFile); dicomJsonModel = storeInstanceResult.dicomJsonModel; dicomFileSaveInfo = storeInstanceResult.dicomFileSaveInfo; - } catch(e) { + this.response.locals.storeInfos.push({ + dicomFileSaveInfo, + dicomJsonModel + }); + } catch (e) { // log transferred failure let auditManager = new AuditManager( EventType.STORE_CREATE, EventOutcomeIndicator.MajorFailure, DicomWebService.getRemoteAddress(this.request), DicomWebService.getRemoteHostname(this.request), DicomWebService.getServerAddress(), DicomWebService.getServerHostname() ); - + await auditManager.onDicomInstancesTransferred( dicomJsonModel ? [dicomJsonModel.uidObj.studyUID] : "Unknown" ); @@ -105,14 +107,6 @@ class StowRsService { throw e; } - - //sync DICOM to FHIR - if (isSyncToFhir) { - let dicomFhirService = new DicomFhirService(this.request, dicomJsonModel); - await dicomFhirService.initDicomFhirConverter(); - await dicomFhirService.postDicomToFhirServerAndStoreLog(); - } - //generate JPEG let dicomJpegGenerator = new DicomJpegGenerator(dicomJsonModel, dicomFileSaveInfo.instancePath); dicomJpegGenerator.generateAllFrames(); diff --git a/api/dicom-web/controller/STOW-RS/storeInstance.js b/api/dicom-web/controller/STOW-RS/storeInstance.js index 2cbbf5bd..b834cc76 100644 --- a/api/dicom-web/controller/STOW-RS/storeInstance.js +++ b/api/dicom-web/controller/STOW-RS/storeInstance.js @@ -23,7 +23,7 @@ class StoreInstanceController extends Controller { let multipartParseResult = await requestMultipartParser.parse(); if (multipartParseResult.status) { - let stowRsService = new StowRsService(this.request, multipartParseResult.multipart.files); + let stowRsService = new StowRsService(this.request, this.response, multipartParseResult.multipart.files); let storeInstancesResult = await stowRsService.storeInstances(); retCode = storeInstancesResult.code; diff --git a/config-class.js b/config-class.js index 40580adb..0d209cbc 100644 --- a/config-class.js +++ b/config-class.js @@ -56,13 +56,6 @@ class DicomWebConfig { } } -class FhirConfig { - constructor() { - this.isSyncToFhir = env.get("SYCN_TO_FHIR_SERVER").default("true").asBool(); - this.baseUrl = env.get("FHIRSERVER_BASE_URL").default("http://127.0.0.1:8089/fhir").asString(); - } -} - class RaccoonConfig { constructor() { @@ -72,7 +65,6 @@ class RaccoonConfig { this.dicomWebConfig = new DicomWebConfig(); this.dicomDimseConfig = new DimseConfig(); - this.fhirConfig = new FhirConfig(); /** @type {string} */ this.mediaStorageUID = generateUidFromGuid( diff --git a/plugins/config.template.js b/plugins/config.template.js index c53654d0..ecd56b1a 100644 --- a/plugins/config.template.js +++ b/plugins/config.template.js @@ -12,5 +12,20 @@ module.exports.pluginsConfig = { method: "get" } ] + }, + "syncToFhirServer": { + enable: false, + before: false, + routers: [ + { + path: "/dicom-web/studies", + method: "post" + } + ], + fhir: { + server: { + baseUrl: "http://127.0.0.1/fhir" + } + } } }; \ No newline at end of file diff --git a/models/FHIR/DICOM/DICOMToFHIR.js b/plugins/syncToFhirServer/DICOMToFHIR.js similarity index 98% rename from models/FHIR/DICOM/DICOMToFHIR.js rename to plugins/syncToFhirServer/DICOMToFHIR.js index 67bb3b1d..3013c1f8 100644 --- a/models/FHIR/DICOM/DICOMToFHIR.js +++ b/plugins/syncToFhirServer/DICOMToFHIR.js @@ -1,12 +1,12 @@ const { URL } = require("url"); const axios = require("axios").default; const _ = require("lodash"); -const { urlJoin } = require("../../../utils/url"); -const { fhirLogger } = require("../../../utils/logs/log"); +const { urlJoin } = require("../../utils/url"); +const { fhirLogger } = require("../../utils/logs/log"); const { getModalitiesInStudy } = require("@dbModels/instance.model"); const { DicomJsonToFhir } = require("dicomjson-to-fhir"); -class DICOMFHIRConverter { +class DicomFhirConverter { constructor() { this.dicomFHIR = { patient: {}, @@ -329,4 +329,4 @@ class DICOMFHIRConverter { } } -module.exports.DICOMFHIRConverter = DICOMFHIRConverter; +module.exports.DicomFhirConverter = DicomFhirConverter; diff --git a/api/dicom-web/controller/STOW-RS/service/dicom-fhir.service.js b/plugins/syncToFhirServer/dicom-fhir.service.js similarity index 78% rename from api/dicom-web/controller/STOW-RS/service/dicom-fhir.service.js rename to plugins/syncToFhirServer/dicom-fhir.service.js index b06d8cec..3837fce1 100644 --- a/api/dicom-web/controller/STOW-RS/service/dicom-fhir.service.js +++ b/plugins/syncToFhirServer/dicom-fhir.service.js @@ -1,35 +1,46 @@ +const path = require("path"); +const fs = require("fs"); + const mongoose = require("mongoose"); const { - DICOMFHIRConverter -} = require("../../../../../models/FHIR/DICOM/DICOMToFHIR"); -const { fhirLogger } = require("../../../../../utils/logs/log"); + DicomFhirConverter +} = require("./DICOMToFHIR"); +const { fhirLogger } = require("../../utils/logs/log"); -const { raccoonConfig } = require("../../../../../config-class"); +const { raccoonConfig } = require("../../config-class"); const { apiPath: DICOM_WEB_API_PATH } = raccoonConfig.dicomWebConfig; -const { - baseUrl: FHIR_BASE_URL -} = raccoonConfig.fhirConfig; + +let pluginConfigFile = path.join(__dirname, "../config.template.js"); +if (fs.existsSync(path.join(__dirname, "../config.js"))) { + pluginConfigFile = path.join(__dirname, "../config.js"); +} + +const fhirBaseUrl = require(pluginConfigFile).pluginsConfig?.syncToFhirServer?.fhir?.server?.baseUrl; class DicomFhirService { constructor(req, dicomJsonModel) { + if (!fhirBaseUrl) { + throw new Error("missing fhir config in your plugin config"); + } + this.request = req; this.dicomJsonModel = dicomJsonModel; /** * @private */ - this.dicomFhirConverter = new DICOMFHIRConverter(); + this.dicomFhirConverter = new DicomFhirConverter(); } async initDicomFhirConverter() { this.dicomFhirConverter.dicomWeb.name =`raccoon-dicom-web-server`; let protocol = this.request.secure ? "https" : "http"; this.dicomFhirConverter.dicomWeb.retrieveStudiesUrl = `${protocol}://${this.request.headers.host}/${DICOM_WEB_API_PATH}/studies`; - this.dicomFhirConverter.fhir.baseUrl = FHIR_BASE_URL; + this.dicomFhirConverter.fhir.baseUrl = fhirBaseUrl; } async postDicomToFhirServerAndStoreLog() { diff --git a/plugins/syncToFhirServer/index.js b/plugins/syncToFhirServer/index.js new file mode 100644 index 00000000..85f64796 --- /dev/null +++ b/plugins/syncToFhirServer/index.js @@ -0,0 +1,30 @@ +require("./mongoose/syncFHIRLog"); +const path = require("path"); +const fs = require("fs"); +const { DicomFhirService } = require("./dicom-fhir.service"); +let pluginConfigFile = path.join(__dirname, "../config.template.js"); + +if (fs.existsSync(path.join(__dirname, "../config.js"))) pluginConfigFile = path.join(__dirname, "../config.js"); + +const { pluginsConfig } = require(pluginConfigFile); + +/** + * + * @param {import("express").Request} req + * @param {import("express").Response} res + * @returns + */ +module.exports = async function (req, res) { + if (!pluginsConfig?.syncToFhirServer?.enable) return; + + setImmediate(async () => { + for (let i = 0; i < res.locals.storeInfos.length; i++) { + /** @type { import("./storeInfo").StoreInfo } */ + let storeInfo = res.locals.storeInfos[i]; + let dicomFhirService = new DicomFhirService(req, storeInfo.dicomJsonModel); + await dicomFhirService.initDicomFhirConverter(); + await dicomFhirService.postDicomToFhirServerAndStoreLog(); + } + }); + +}; \ No newline at end of file diff --git a/models/mongodb/models/syncFHIRLog.js b/plugins/syncToFhirServer/mongoose/syncFHIRLog.js similarity index 100% rename from models/mongodb/models/syncFHIRLog.js rename to plugins/syncToFhirServer/mongoose/syncFHIRLog.js diff --git a/plugins/syncToFhirServer/storeInfo.d.ts b/plugins/syncToFhirServer/storeInfo.d.ts new file mode 100644 index 00000000..e73e0f91 --- /dev/null +++ b/plugins/syncToFhirServer/storeInfo.d.ts @@ -0,0 +1,7 @@ +import type { DicomJsonModel } from "@models/DICOM/dicom-json-model" +import type { DicomFileSaveInfo } from "@root/utils/typeDef/STOW-RS/STOW-RS" + +export type StoreInfo = { + dicomFileSaveInfo: DicomFileSaveInfo, + dicomJsonModel: DicomJsonModel +} \ No newline at end of file From 2904d45bcf1efb7765abc8004352b3756fb90dc4 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sat, 20 Jan 2024 15:24:04 +0800 Subject: [PATCH 354/365] fix: stow rs service usage missing res parameter --- dimse/c-store.js | 2 +- local/dicom-uploader.js | 2 +- test/query.test.js | 1 - test/store-instances.test.js | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dimse/c-store.js b/dimse/c-store.js index 6461da5c..4c866a5e 100644 --- a/dimse/c-store.js +++ b/dimse/c-store.js @@ -31,7 +31,7 @@ const cStoreScpInjectProxy = createCStoreSCPInjectProxy({ socket: { remoteAddress: await association.getCallingAET() } - }, []); + }, { locals: {} }, []); /** @type {formidable.File} */ let fileObj = { diff --git a/local/dicom-uploader.js b/local/dicom-uploader.js index 941de343..b564c57d 100644 --- a/local/dicom-uploader.js +++ b/local/dicom-uploader.js @@ -43,7 +43,7 @@ async function storeInstance(filePath) { host: "fake-host" }, params: {} - }, []); + }, { locals: {} }, []); /** @type {formidable.File} */ let fileObj = { diff --git a/test/query.test.js b/test/query.test.js index 6b329aef..eb9f61de 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -3,7 +3,6 @@ const formidable = require("formidable"); const glob = require("glob"); const path = require("path"); const fsP = require("fs/promises"); -const { StowRsService } = require("../api/dicom-web/controller/STOW-RS/service/stow-rs.service"); const patientModel = require("../models/mongodb/models/patient.model"); const dicomStudyModel = require("../models/mongodb/models/study.model"); const dicomSeriesModel = require("../models/mongodb/models/series.model"); diff --git a/test/store-instances.test.js b/test/store-instances.test.js index e72c206e..b89976a5 100644 --- a/test/store-instances.test.js +++ b/test/store-instances.test.js @@ -16,7 +16,7 @@ async function storeDicomInstancesAndGet4Patients() { host: "fake-host" }, params: {} - }, []); + }, { locals: {} }, []); /** @type {string[]} */ let files = glob.sync("**/*.dcm", { From e3fa12b7e2468fbbbaa3e379e2a8984186e631a3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 21 Jan 2024 15:57:46 +0800 Subject: [PATCH 355/365] fix: missing assign `apiLogger` in `this` of `storeInstanceController` --- api/dicom-web/controller/STOW-RS/storeInstance.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/STOW-RS/storeInstance.js b/api/dicom-web/controller/STOW-RS/storeInstance.js index b834cc76..31fb4597 100644 --- a/api/dicom-web/controller/STOW-RS/storeInstance.js +++ b/api/dicom-web/controller/STOW-RS/storeInstance.js @@ -15,8 +15,8 @@ class StoreInstanceController extends Controller { let startSTOWTime = performance.now(); let retCode; let storeMessage; - let apiLogger = new ApiLogger(this.request, "STOW-RS"); - apiLogger.addTokenValue(); + this.apiLogger = new ApiLogger(this.request, "STOW-RS"); + this.apiLogger.addTokenValue(); try { let requestMultipartParser = new StowRsRequestMultipartParser(this.request); @@ -31,7 +31,7 @@ class StoreInstanceController extends Controller { } let endSTOWTime = performance.now(); let elapsedTime = (endSTOWTime - startSTOWTime).toFixed(3); - apiLogger.logger.info(`Finished STOW-RS, elapsed time: ${elapsedTime} ms`); + this.apiLogger.logger.info(`Finished STOW-RS, elapsed time: ${elapsedTime} ms`); this.response.writeHead(retCode, { "Content-Type": "application/dicom" From b68a1235c3fbab447823f017822ece7af65f97db Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 21 Jan 2024 16:05:21 +0800 Subject: [PATCH 356/365] refactor: import `DicomBulkDataModel` property instead of import all --- models/DICOM/dicom-json-model.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/DICOM/dicom-json-model.js b/models/DICOM/dicom-json-model.js index 87c8cd18..3ebbbe25 100644 --- a/models/DICOM/dicom-json-model.js +++ b/models/DICOM/dicom-json-model.js @@ -9,7 +9,7 @@ const { } = require("./dcmtk"); const flat = require("flat"); const shortHash = require("shorthash2"); -const dicomBulkDataModel = require("@dbModels/dicomBulkData.model"); +const { DicomBulkDataModel } = require("@dbModels/dicomBulkData.model"); const { logger } = require("../../utils/logs/log"); const { tagsNeedStore } = require("./dicom-tags-mapping"); @@ -591,7 +591,7 @@ class BulkData { binaryValuePath: this.pathOfBinaryProperty }; - await dicomBulkDataModel.createOrUpdateBulkData( + await DicomBulkDataModel.createOrUpdateBulkData( { instanceUID: this.uidObj.instanceUID, binaryValuePath: this.pathOfBinaryProperty From 92203aaee4ea7d31666ebee341295c09642ce408 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 21 Jan 2024 16:56:44 +0800 Subject: [PATCH 357/365] refactor: use `StudyModel`, `SeriesModel` instead of `mongoose.model` --- .../controller/WADO-RS/service/WADO-RS.service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js index 5d7fd55f..2cc75361 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js +++ b/api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js @@ -1,12 +1,13 @@ const _ = require("lodash"); const path = require("path"); const fsP = require("fs/promises"); -const mongoose = require("mongoose"); const { MultipartWriter } = require("../../../../../utils/multipartWriter"); const errorResponse = require("../../../../../utils/errorResponse/errorResponseMessage"); const { raccoonConfig } = require("../../../../../config-class"); const { JSONPath } = require("jsonpath-plus"); const { DicomWebService } = require("../../../service/dicom-web.service"); +const { StudyModel } = require("@dbModels/study.model"); +const { SeriesModel } = require("@dbModels/series.model"); const { InstanceModel } = require("@dbModels/instance.model"); const { logger } = require("../../../../../utils/logs/log"); const { RetrieveAuditService } = require("./retrieveAudit.service"); @@ -150,7 +151,7 @@ class StudyImagePathFactory extends ImagePathFactory { } async getImagePaths() { - this.imagePaths = await mongoose.model("dicomStudy").getPathGroupOfInstances(this.uids); + this.imagePaths = await StudyModel.getPathGroupOfInstances(this.uids); } } @@ -160,7 +161,7 @@ class SeriesImagePathFactory extends ImagePathFactory { } async getImagePaths() { - this.imagePaths = await mongoose.model("dicomSeries").getPathGroupOfInstances(this.uids); + this.imagePaths = await SeriesModel.getPathGroupOfInstances(this.uids); } } From c212fd0256be626e77733ac1ac86fb05949b57ec Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Sun, 21 Jan 2024 21:13:47 +0800 Subject: [PATCH 358/365] feat: only add events when `hitSubscriptions` greater equal than 1 --- api/dicom-web/controller/UPS-RS/service/cancel.service.js | 7 ++++--- .../controller/UPS-RS/service/create-workItem.service.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/api/dicom-web/controller/UPS-RS/service/cancel.service.js b/api/dicom-web/controller/UPS-RS/service/cancel.service.js index 05a53537..5e6ed5ea 100644 --- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js +++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js @@ -59,9 +59,10 @@ class CancelWorkItemService extends BaseWorkItemService { async addCancelEvent() { let hitSubscriptions = await this.getHitSubscriptions(this.workItem); - let aeTitles = hitSubscriptions.map(v => v.aeTitle); - - this.addUpsEvent(UPS_EVENT_TYPE.CancelRequested, this.upsInstanceUID, this.cancelRequestBy(), aeTitles); + if (hitSubscriptions.length > 0 ) { + let aeTitles = hitSubscriptions.map(v => v.aeTitle); + this.addUpsEvent(UPS_EVENT_TYPE.CancelRequested, this.upsInstanceUID, this.cancelRequestBy(), aeTitles); + } } cancelRequestBy() { diff --git a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js index 75e1c314..d3355c9d 100644 --- a/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js +++ b/api/dicom-web/controller/UPS-RS/service/create-workItem.service.js @@ -86,7 +86,7 @@ class CreateWorkItemService extends BaseWorkItemService { let hitSubscriptions = await this.getHitSubscriptions(workItem); - if (hitSubscriptions) { + if (hitSubscriptions?.length > 0 ) { let hitSubscriptionAeTitleArray = hitSubscriptions.map(sub => sub.aeTitle); this.addUpsEvent(UPS_EVENT_TYPE.StateReport, workItem.upsInstanceUID, this.stateReportOf(await workItem.toDicomJson()), hitSubscriptionAeTitleArray); let assignedEventInformationArray = await this.getAssignedEventInformationArray( From a731375d7e9e183184cb8b46de8f0f8d3d9f2bb8 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 22 Jan 2024 12:20:21 +0800 Subject: [PATCH 359/365] feat: add `findOneByPatientID` to wrap mongoose `findOne` method --- .../controller/PAM-RS/service/delete-patient.service.js | 2 +- models/mongodb/models/patient.model.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js b/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js index e6ad0cd3..b1eb680a 100644 --- a/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js +++ b/api/dicom-web/controller/PAM-RS/service/delete-patient.service.js @@ -11,7 +11,7 @@ class DeletePatientService { async delete() { let { patientID } = this.request.params; - let patient = await PatientModel.findOne({ patientID}); + let patient = await PatientModel.findOneByPatientID(patientID); if (!patient) { throw new DicomWebServiceError( DicomWebStatusCodes.NoSuchObjectInstance, diff --git a/models/mongodb/models/patient.model.js b/models/mongodb/models/patient.model.js index 9905beb4..3c29d878 100644 --- a/models/mongodb/models/patient.model.js +++ b/models/mongodb/models/patient.model.js @@ -70,6 +70,11 @@ let patientSchemaOptions = _.merge( return patient; }, + findOneByPatientID: async function(patientID) { + return await mongoose.model("patient").findOne({ + patientID + }); + }, /** * * @param {string} patientID From feb1ed160421d90865f47a43d29966ddd0a0c227 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 22 Jan 2024 12:51:50 +0800 Subject: [PATCH 360/365] feat: add `findOneByDicomUID` for study/series/instance models and use for delete --- api/dicom-web/controller/WADO-RS/deletion/service/delete.js | 6 +++--- models/mongodb/models/instance.model.js | 3 +++ models/mongodb/models/series.model.js | 3 +++ models/mongodb/models/study.model.js | 3 +++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js index 9b4a33c7..977a9d2e 100644 --- a/api/dicom-web/controller/WADO-RS/deletion/service/delete.js +++ b/api/dicom-web/controller/WADO-RS/deletion/service/delete.js @@ -28,7 +28,7 @@ class DeleteService { } async deleteStudy() { - let study = await StudyModel.findOne({ + let study = await StudyModel.findOneByDicomUID({ ...this.request.params }); @@ -46,7 +46,7 @@ class DeleteService { } async deleteSeries() { - let aSeries = await SeriesModel.findOne({ + let aSeries = await SeriesModel.findOneByDicomUID({ ...this.request.params }); @@ -64,7 +64,7 @@ class DeleteService { async deleteInstance() { - let instance = await InstanceModel.findOne({ + let instance = await InstanceModel.findOneByDicomUID({ ...this.request.params }); diff --git a/models/mongodb/models/instance.model.js b/models/mongodb/models/instance.model.js index d60bdef7..afb00213 100644 --- a/models/mongodb/models/instance.model.js +++ b/models/mongodb/models/instance.model.js @@ -34,6 +34,9 @@ let dicomSchemaOptions = _.merge( { strict: false, methods: { + findOneByDicomUID: async function ({ studyUID, seriesUID, instanceUID }) { + return await mongoose.model("dicom").findOne({ studyUID, seriesUID, instanceUID }).exec(); + }, deleteDicomInstances: async function () { let instancePath = this.instancePath; try { diff --git a/models/mongodb/models/series.model.js b/models/mongodb/models/series.model.js index 72e32bdd..4b3501d4 100644 --- a/models/mongodb/models/series.model.js +++ b/models/mongodb/models/series.model.js @@ -34,6 +34,9 @@ let dicomSeriesSchemaOptions = _.merge( } }, statics: { + findOneByDicomUID: async function({ studyUID, seriesUID }) { + return await mongoose.model("dicomSeries").findOne({ studyUID, seriesUID }).exec(); + }, /** * * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions diff --git a/models/mongodb/models/study.model.js b/models/mongodb/models/study.model.js index 4d1df6e8..2b0cc386 100644 --- a/models/mongodb/models/study.model.js +++ b/models/mongodb/models/study.model.js @@ -44,6 +44,9 @@ let dicomStudySchemaOptions = _.merge( } }, statics: { + findOneByDicomUID: async function({ studyUID }) { + return await mongoose.model("dicomStudy").findOne({ studyUID }).exec(); + }, /** * * @param {import("@root/utils/typeDef/dicom").DicomJsonQueryOptions} queryOptions From 46031c9763f2de8b12d86ce4e93f5916be8e74a3 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 22 Jan 2024 13:45:34 +0800 Subject: [PATCH 361/365] fix: move `findOneByDicomUID` to static --- models/mongodb/models/instance.model.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/mongodb/models/instance.model.js b/models/mongodb/models/instance.model.js index afb00213..ca2455b8 100644 --- a/models/mongodb/models/instance.model.js +++ b/models/mongodb/models/instance.model.js @@ -34,9 +34,6 @@ let dicomSchemaOptions = _.merge( { strict: false, methods: { - findOneByDicomUID: async function ({ studyUID, seriesUID, instanceUID }) { - return await mongoose.model("dicom").findOne({ studyUID, seriesUID, instanceUID }).exec(); - }, deleteDicomInstances: async function () { let instancePath = this.instancePath; try { @@ -54,6 +51,9 @@ let dicomSchemaOptions = _.merge( } }, statics: { + findOneByDicomUID: async function ({ studyUID, seriesUID, instanceUID }) { + return await mongoose.model("dicom").findOne({ studyUID, seriesUID, instanceUID }).exec(); + }, getAuditInstancesInfoFromStudyUID: async (studyUID) => { let instances = await mongoose.model("dicom").find({ studyUID }).exec(); From db9e488ac07f9eaad02a34ea729e57974d60dc0f Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Mon, 22 Jan 2024 15:28:05 +0800 Subject: [PATCH 362/365] feat: add `updateStatusByQuery` for `MwlItemModel` instead findAll --- .../service/change-filtered-mwlItem-status.js | 15 ++------------- models/mongodb/models/mwlitems.model.js | 11 ++++++++++- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js index 6ff7bcfe..d35b03ea 100644 --- a/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js +++ b/api/dicom-web/controller/MWL-RS/service/change-filtered-mwlItem-status.js @@ -12,21 +12,10 @@ class ChangeFilteredMwlItemStatusService extends BaseQueryService { async changeMwlItemsStatus() { let { status } = this.request.params; - let mwlItems = await this.getMwlItems(); - if (mwlItems.length === 0) { - throw new DicomWebServiceError(DicomWebStatusCodes.NoSuchObjectInstance, "Can not found any MWL item from query", 404); - } - for (let mwlItem of mwlItems) { - _.set(mwlItem, `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus}.Value.0`, status); - await mwlItem.save(); - } + let updatedCount = await MwlItemModel.updateStatusByQuery(this.query, status); - return mwlItems.length; - } - - async getMwlItems() { - return await MwlItemModel.findMwlItems(this.query); + return updatedCount; } } diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index 9d53e3d5..e9b20bb6 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -103,7 +103,16 @@ let mwlItemSchema = new mongoose.Schema( }); return await mwlItem.save(); }, - findMwlItems: async function(query) { + updateStatusByQuery: async function (query, status) { + let mongoQuery = (await convertRequestQueryToMongoQuery(query)).$match; + let updateResult = await mongoose.model("mwlItems").updateMany(mongoQuery, { + $set: { + [`${dictionary.keyword.ScheduledProcedureStepSequence.tag}.Value.0.${dictionary.keyword.ScheduledProcedureStepStatus.tag}.Value.0`]: status + } + }).exec(); + return updateResult.modifiedCount; + }, + findMwlItems: async function (query) { let mongoQuery = await convertRequestQueryToMongoQuery(query); return await mongoose.model("mwlItems").find(mongoQuery); } From 9388f0d167d3c81d88b74bbef3bfae9844a0915e Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Tue, 23 Jan 2024 18:21:24 +0800 Subject: [PATCH 363/365] refactor: use the `7zip-min` module instead of the `archiver` module - Add a new file `utils/sevenZip.js` that contains the `SevenZip` class - Replaced `archiver` package usage with `SevenZip` class in `WADOZip.js` - Replaced `toZip` function with `#streamZipToResponse` method in `WADOZip.js` - Created new file `sevenZip.js` in `utils` directory - Added `SevenZip` class to `sevenZip.js` - Added type definitions file `sevenZip.d.ts` in `utils/typeDef` directory --- .../controller/WADO-RS/service/WADOZip.js | 100 ++-- package-lock.json | 426 +++--------------- package.json | 2 +- utils/sevenZip.js | 86 ++++ utils/typeDef/sevenZip.d.ts | 3 + 5 files changed, 194 insertions(+), 423 deletions(-) create mode 100644 utils/sevenZip.js create mode 100644 utils/typeDef/sevenZip.d.ts diff --git a/api/dicom-web/controller/WADO-RS/service/WADOZip.js b/api/dicom-web/controller/WADO-RS/service/WADOZip.js index afadc23b..73a1a9d0 100644 --- a/api/dicom-web/controller/WADO-RS/service/WADOZip.js +++ b/api/dicom-web/controller/WADO-RS/service/WADOZip.js @@ -1,8 +1,10 @@ -const archiver = require("archiver"); +const fs = require("fs"); const path = require("path"); const { StudyModel } = require("@dbModels/study.model"); const { SeriesModel } = require("@dbModels/series.model"); const { InstanceModel } = require("@dbModels/instance.model"); +const { SevenZip } = require("@root/utils/sevenZip"); +const { v4: uuidV4 } = require("uuid"); class WADOZip { constructor(iReq, iRes) { this.requestParams = iReq.params; @@ -21,16 +23,13 @@ class WADOZip { async getZipOfStudyDICOMFiles() { let imagesPathList = await StudyModel.getPathGroupOfInstances(this.requestParams); if (imagesPathList.length > 0) { - this.setHeaders(this.studyUID); + await this.#streamZipToResponse(imagesPathList); - let folders = []; - for (let i = 0; i < imagesPathList.length; i++) { - let imagesFolder = path.dirname(imagesPathList[i].instancePath); - if (!folders.includes(imagesFolder)) { - folders.push(imagesFolder); - } - } - return await toZip(this.res, folders); + return { + status: true, + code: 200, + message: "success" + }; } return { status: false, @@ -42,12 +41,12 @@ class WADOZip { async getZipOfSeriesDICOMFiles() { let imagesPathList = await SeriesModel.getPathGroupOfInstances(this.requestParams); if (imagesPathList.length > 0) { - this.setHeaders(this.seriesUID); - - let folders = []; - let seriesPath = path.dirname(imagesPathList[0].instancePath); - folders.push(seriesPath); - return await toZip(this.res, folders); + await this.#streamZipToResponse(imagesPathList); + return { + status: true, + code: 200, + message: "success" + }; } return { status: false, @@ -59,10 +58,12 @@ class WADOZip { async getZipOfInstanceDICOMFile() { let imagePath = await InstanceModel.getPathOfInstance(this.requestParams); if (imagePath) { - this.setHeaders(this.instanceUID); - - - return await toZip(this.res, [], imagePath.instancePath); + await this.#streamZipToResponse([imagePath]); + return { + status: true, + code: 200, + message: "success" + }; } return { status: false, @@ -70,40 +71,37 @@ class WADOZip { message: `not found, Instance UID: ${this.requestParams.instanceUID}, Series UID: ${this.requestParams.seriesUID}, Study UID: ${this.requestParams.studyUID}` }; } -} -function toZip(res, folders = [], filename = "") { - return new Promise((resolve) => { - let archive = archiver('zip', { - gzip: true, - zlib: { level: 9 } // Sets the compression level. - }); - archive.on('error', function (err) { - console.error(err); - resolve({ - status: false, - code: 500, - data: err - }); + async #streamZipToResponse(imagesPathList) { + let randomFilename = uuidV4(); + this.setHeaders(randomFilename); + + let zipFile = path.join(__dirname, `../../../../../tempUploadFiles/${randomFilename}.zip`); + await ZipFactory.compressToZipFile(zipFile, imagesPathList); + + fs.createReadStream(zipFile).pipe(this.res); + + this.res.on("finish", () => { + fs.unlink(zipFile, () => { }); }); - archive.pipe(res); - if (folders.length > 0) { - for (let i = 0; i < folders.length; i++) { - let folderName = path.basename(folders[i]); - //archive.append(null, {name : folderName}); - archive.glob("*.dcm", { cwd: folders[i] }, { prefix: folderName }); - } - } else { - archive.file(filename); + } +} + +class ZipFactory { + static async compressToZipFile(zipFile, imagePaths) { + let sevenZip = new SevenZip("zip", undefined, zipFile); + + for (let i = 0; i < imagePaths.length; i++) { + sevenZip.addCmd(imagePaths[i].instancePath); } - archive.finalize().then(() => { - resolve({ - status: true, - code: 200, - data: "success" - }); - }); - }); + + // prevent same filename + if (imagePaths.length >= 2) + sevenZip.useFullyQualifiedFilePaths(); + + sevenZip.overwrite("a"); + await sevenZip.pack(); + } } module.exports.WADOZip = WADOZip; diff --git a/package-lock.json b/package-lock.json index d5a63126..7e8547df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", - "archiver": "^5.3.1", + "7zip-min": "^1.4.4", "axios": "^1.6.2", "bcrypt": "^5.0.1", "body-parser": "^1.20.0", @@ -1576,6 +1576,19 @@ "@types/webidl-conversions": "*" } }, + "node_modules/7zip-bin": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz", + "integrity": "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==" + }, + "node_modules/7zip-min": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/7zip-min/-/7zip-min-1.4.4.tgz", + "integrity": "sha512-mYB1WW5tcXfZxUN4+2joKk4+6j8jp+mpO2YiMU5z1gNNFbACxI2ADasffsdNPovZSwn/E662ZIH5gRkFPMufmA==", + "dependencies": { + "7zip-bin": "5.1.1" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1699,70 +1712,6 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, - "node_modules/archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/archiver-utils/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", @@ -1826,11 +1775,6 @@ "node": "*" } }, - "node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" - }, "node_modules/async-mutex": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", @@ -2024,6 +1968,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, "engines": { "node": "*" } @@ -2313,20 +2258,6 @@ "dot-prop": "^5.1.0" } }, - "node_modules/compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -2744,7 +2675,8 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true }, "node_modules/cors": { "version": "2.8.5", @@ -2758,29 +2690,6 @@ "node": ">= 0.10" } }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -3130,6 +3039,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "dependencies": { "once": "^1.4.0" } @@ -3771,7 +3681,8 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true }, "node_modules/fs-minipass": { "version": "2.1.0", @@ -4465,7 +4376,8 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -4785,44 +4697,6 @@ "node": ">8" } }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/lazystream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/lazystream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4884,21 +4758,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -4917,11 +4776,6 @@ "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", "dev": true }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4935,11 +4789,6 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" - }, "node_modules/log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -6094,6 +5943,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -6626,7 +6476,8 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -6878,14 +6729,6 @@ "node": ">= 6" } }, - "node_modules/readdir-glob": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "dependencies": { - "minimatch": "^3.0.4" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7770,6 +7613,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -7785,6 +7629,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -8259,19 +8104,6 @@ "engines": { "node": "^12.20.0 || >=14" } - }, - "node_modules/zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dependencies": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } } }, "dependencies": { @@ -9554,6 +9386,19 @@ "@types/webidl-conversions": "*" } }, + "7zip-bin": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz", + "integrity": "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==" + }, + "7zip-min": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/7zip-min/-/7zip-min-1.4.4.tgz", + "integrity": "sha512-mYB1WW5tcXfZxUN4+2joKk4+6j8jp+mpO2YiMU5z1gNNFbACxI2ADasffsdNPovZSwn/E662ZIH5gRkFPMufmA==", + "requires": { + "7zip-bin": "5.1.1" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -9644,66 +9489,6 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, - "archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", @@ -9758,11 +9543,6 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" - }, "async-mutex": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", @@ -9909,7 +9689,8 @@ "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true }, "buffer-from": { "version": "1.1.2", @@ -10131,17 +9912,6 @@ "dot-prop": "^5.1.0" } }, - "compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -10466,7 +10236,8 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true }, "cors": { "version": "2.8.5", @@ -10477,20 +10248,6 @@ "vary": "^1" } }, - "crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" - }, - "crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - } - }, "cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -10753,6 +10510,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "requires": { "once": "^1.4.0" } @@ -11243,7 +11001,8 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true }, "fs-minipass": { "version": "2.1.0", @@ -11753,7 +11512,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -11966,43 +11726,6 @@ "asn1.js": "^5.4.1" } }, - "lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "requires": { - "readable-stream": "^2.0.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -12054,21 +11777,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -12087,11 +11795,6 @@ "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", "dev": true }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12105,11 +11808,6 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, - "lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" - }, "log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -12932,7 +12630,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "npmlog": { "version": "5.0.1", @@ -13331,7 +13030,8 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "proxy-addr": { "version": "2.0.7", @@ -13528,14 +13228,6 @@ "util-deprecate": "^1.0.1" } }, - "readdir-glob": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "requires": { - "minimatch": "^3.0.4" - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -14192,6 +13884,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -14204,6 +13897,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -14558,16 +14252,6 @@ "optional": true } } - }, - "zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - } } } } diff --git a/package.json b/package.json index 2145072f..8f892e5c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "license": "MIT", "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", - "archiver": "^5.3.1", + "7zip-min": "^1.4.4", "axios": "^1.6.2", "bcrypt": "^5.0.1", "body-parser": "^1.20.0", diff --git a/utils/sevenZip.js b/utils/sevenZip.js new file mode 100644 index 00000000..61de6c33 --- /dev/null +++ b/utils/sevenZip.js @@ -0,0 +1,86 @@ +const SevenZipMin = require("7zip-min"); + +class SevenZip { + + /** @type { import("@root/utils/typeDef/sevenZip").archiveType } */ + #type; + /** @type { string } */ + #source; + /** @type { string } */ + #dest; + constructor(type = "zip", source = "", dest = "") { + this.#type = type; + this.#source = source; + this.#dest = dest; + /** @type {string[]} */ + this.cmd = []; + } + + /** + * + * @param { import("@root/utils/typeDef/sevenZip").archiveType } value + */ + setType(value) { + this.#type = value; + return this; + } + + setSource(value) { + this.#source = value; + return this; + } + + setDest(value) { + this.#dest = value; + return this; + } + + addCmd(iCmd) { + this.cmd.push(iCmd); + return this; + } + + recursive() { + if (this.cmd.includes("-r")) throw new Error("already set recursive"); + this.cmd.push("-r"); + return this; + } + + useFullyQualifiedFilePaths() { + if (this.cmd.includes("-spf2")) throw new Error("already set useFullyQualifiedFilePaths"); + this.cmd.push("-spf2"); + return this; + } + + /** + * + * @param {import("@root/utils/typeDef/sevenZip").overwriteMode} mode + */ + overwrite(mode) { + if (this.cmd.find(v => v.startsWith("-ao"))) throw new Error("already set overwrite"); + this.cmd.push(`-ao${mode}`); + return this; + } + + /** + * run 7zip command: + * + * 7z a -t{type} {dest} {source} {...additionalCmd} + * @returns + */ + async pack() { + return new Promise((resolve, reject) => { + let cmd = ["a", `-t${this.#type}`, this.#dest, this.#source, ...this.cmd]; + if (!this.#source) cmd = ["a", `-t${this.#type}`, this.#dest, ...this.cmd]; + SevenZipMin.cmd(cmd, err => { + if (err) { + reject(err); + } + + resolve(); + }); + }); + } +} + +module.exports.SevenZip = SevenZip; \ No newline at end of file diff --git a/utils/typeDef/sevenZip.d.ts b/utils/typeDef/sevenZip.d.ts new file mode 100644 index 00000000..2a2d7a55 --- /dev/null +++ b/utils/typeDef/sevenZip.d.ts @@ -0,0 +1,3 @@ +export type archiveType = "7z" | "zip" | "gzip" | "bzip2" | "tar"; + +export type overwriteMode = "a" | "s" | "u" | "t"; From b37f17b564eaeeacf8176dbb9be25f67b96fc690 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 24 Jan 2024 14:01:56 +0800 Subject: [PATCH 364/365] chore: remove unused code --- utils/multipartWriter.js | 52 ---------------------------------------- 1 file changed, 52 deletions(-) diff --git a/utils/multipartWriter.js b/utils/multipartWriter.js index 25d5963e..3764cb35 100644 --- a/utils/multipartWriter.js +++ b/utils/multipartWriter.js @@ -2,7 +2,6 @@ const { logger } = require("../utils/logs/log"); const uuid = require("uuid"); const fs = require("fs"); const _ = require("lodash"); -const dicomParser = require("dicom-parser"); const { streamToBuffer } = require("@jorgeferrero/stream-to-buffer"); const { Dcm2JpgExecutor } = require("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor"); const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions"); @@ -147,57 +146,6 @@ class MultipartWriter { } } - /** - * Write image files of frames in multipart content - * @param {string} type - * @param {Array} frameList - * @returns - */ - async writeFrames(type, frameList) { - this.setHeaderMultipartRelatedContentType(); - let dicomFilename = `${this.imagePathObjList[0].instancePath}`; - let jpegFile = dicomFilename.replace(/\.dcm\b/gi, ""); - let minFrameNumber = _.min(frameList); - let maxFrameNumber = _.max(frameList); - let frameNumberCount = maxFrameNumber - minFrameNumber + 1; - if (minFrameNumber == maxFrameNumber) { - frameNumberCount = 1; - } - - try { - - for (let i = 1; i <= frameNumberCount; i++) { - /** @type {Dcm2JpgExecutor$Dcm2JpgOptions} */ - let opt = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync(); - opt.frameNumber = i; - await Dcm2JpgExecutor.convertDcmToJpgFromFilename(dicomFilename, `${jpegFile}.${i - 1}.jpg`, opt); - } - - for (let x = 0; x < frameList.length; x++) { - let frameJpegFile = dicomFilename.replace( - /\.dcm\b/gi, - `.${frameList[x] - 1}.jpg` - ); - let fileBuffer = fs.readFileSync(frameJpegFile); - let dicomFileBuffer = fs.readFileSync(dicomFilename); - let dicomDataSet = dicomParser.parseDicom(dicomFileBuffer, { - untilTag: "x7fe00010" - }); - let transferSyntax = dicomDataSet.string("x00020010"); - this.writeBoundary(); - this.writeContentType(type, transferSyntax); - this.writeContentLength(fileBuffer.length); - this.writeContentLocation(); - this.writeBufferData(fileBuffer); - } - this.writeFinalBoundary(); - return true; - } catch (e) { - logger.error(e); - return false; - } - } - writeBuffer(buffer, headers) { try { this.writeBoundary(); From 73a4dc7620285f00eeeacbc5e7b2251d09083957 Mon Sep 17 00:00:00 2001 From: chinlinlee Date: Wed, 24 Jan 2024 14:02:35 +0800 Subject: [PATCH 365/365] chore: remove `dicom-parser` package --- package-lock.json | 11 ----------- package.json | 1 - 2 files changed, 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e8547df..6bcf9c31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "connect-session-sequelize": "^7.1.7", "cookie-parser": "^1.4.6", "cors": "^2.8.5", - "dicom-parser": "^1.8.13", "dicomjson-to-fhir": "^1.0.1", "dotenv": "^16.0.3", "env-var": "^7.3.1", @@ -2893,11 +2892,6 @@ "wrappy": "1" } }, - "node_modules/dicom-parser": { - "version": "1.8.13", - "resolved": "https://registry.npmjs.org/dicom-parser/-/dicom-parser-1.8.13.tgz", - "integrity": "sha512-8M53FPHS4zM3zvu5fdIWdatqrjpiG2+2M6RJ0IxwqLF4gvCYRsqUIusxYaOiNU0sWaptUpnXeZiXunP0LOIcQw==" - }, "node_modules/dicomjson-to-fhir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dicomjson-to-fhir/-/dicomjson-to-fhir-1.0.1.tgz", @@ -10392,11 +10386,6 @@ "wrappy": "1" } }, - "dicom-parser": { - "version": "1.8.13", - "resolved": "https://registry.npmjs.org/dicom-parser/-/dicom-parser-1.8.13.tgz", - "integrity": "sha512-8M53FPHS4zM3zvu5fdIWdatqrjpiG2+2M6RJ0IxwqLF4gvCYRsqUIusxYaOiNU0sWaptUpnXeZiXunP0LOIcQw==" - }, "dicomjson-to-fhir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dicomjson-to-fhir/-/dicomjson-to-fhir-1.0.1.tgz", diff --git a/package.json b/package.json index 8f892e5c..69a2139c 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,6 @@ "connect-session-sequelize": "^7.1.7", "cookie-parser": "^1.4.6", "cors": "^2.8.5", - "dicom-parser": "^1.8.13", "dicomjson-to-fhir": "^1.0.1", "dotenv": "^16.0.3", "env-var": "^7.3.1",