diff --git a/.env.template b/.env.template
index 93d40865..2019778c 100644
--- a/.env.template
+++ b/.env.template
@@ -1,12 +1,12 @@
-# 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"
+SQL_LOGGING=false
+SQL_FORCE_SYNC=false
# Server
SERVER_PORT=8081
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/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..01d5a6fd
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,233 @@
+# Changelog
+
+All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+
+## [1.2.0](https://github.com/Chinlinlee/raccoon-dicom/compare/v1.1.0...v1.2.0) (2023-06-17)
+
+
+### Features
+
+* [#11](https://github.com/Chinlinlee/raccoon-dicom/issues/11) ([7d2d9ed](https://github.com/Chinlinlee/raccoon-dicom/commit/7d2d9ed000d32bbc87aade7d0a16a1eef9d66a20))
+
+
+### Bug Fixes
+
+* wrong config filename of plugin of checking ([4b6de1a](https://github.com/Chinlinlee/raccoon-dicom/commit/4b6de1aa70d02e2e71ff5d86c7da0b57d134f247))
+
+## [1.1.0](https://github.com/Chinlinlee/raccoon-dicom/compare/v1.0.1...v1.1.0) (2023-06-15)
+
+
+### Features
+
+* [#10](https://github.com/Chinlinlee/raccoon-dicom/issues/10) ([f617273](https://github.com/Chinlinlee/raccoon-dicom/commit/f617273cecdb7c764f45e48d76135590d6a7ca9f))
+* add `update workitem` API ([6d921bb](https://github.com/Chinlinlee/raccoon-dicom/commit/6d921bbdcd0983ea190985d47b91e47cf7e5f15b))
+* add base dcm2dcm ([d71d895](https://github.com/Chinlinlee/raccoon-dicom/commit/d71d895f9b221850a621b2d8e8267c771b2efbdf))
+* add dicom error status ([eb69a59](https://github.com/Chinlinlee/raccoon-dicom/commit/eb69a59e18a2ad3df8627008de8a9b2ca2074b03))
+* add MONGODB_OPTIONS to .env ([bbfdd11](https://github.com/Chinlinlee/raccoon-dicom/commit/bbfdd1154f0e5bbcb007bff413faa972c860c36f))
+* add request query to string in controller ([2695ccd](https://github.com/Chinlinlee/raccoon-dicom/commit/2695ccdc82a1bedd892e61c1a47d1bb735eee258))
+* add un subscription API ([28e35f4](https://github.com/Chinlinlee/raccoon-dicom/commit/28e35f41dd81d6daa66901e469677ec155a01607))
+* add update event for subscription ([35347fe](https://github.com/Chinlinlee/raccoon-dicom/commit/35347fe2ab0573f2e01d7f62f8510f6121f739c6))
+* add ups cancel request API ([1d2c07a](https://github.com/Chinlinlee/raccoon-dicom/commit/1d2c07a5b2b4b85ed373960eaa0a93bbfcb511f9))
+* change state event ([08ae3f2](https://github.com/Chinlinlee/raccoon-dicom/commit/08ae3f289207c946934dadd9896c44eae9130bb2))
+* change workitem state API ([eb6ed91](https://github.com/Chinlinlee/raccoon-dicom/commit/eb6ed9187bdcd61a8cbd5081e09c77a0662c5f55))
+* check DIMSE is setup or not ([bb30a34](https://github.com/Chinlinlee/raccoon-dicom/commit/bb30a34b9ad2618d5a4af1b7ce922216821fbb23))
+* global subscription, event for creating ([c985d94](https://github.com/Chinlinlee/raccoon-dicom/commit/c985d948adc48496f693c8487d4d13cd9cac8d81))
+* **qido-rs:** support time query ([9d7ec94](https://github.com/Chinlinlee/raccoon-dicom/commit/9d7ec9468031da3d9ca938dd5d21694c1633b610))
+* retrieve UPS by instance UID ([2712733](https://github.com/Chinlinlee/raccoon-dicom/commit/2712733aecf9ab6ba17db979ab456ed870eaaa3d))
+* subscription API ([98b3f34](https://github.com/Chinlinlee/raccoon-dicom/commit/98b3f345003fcefb0df11339322596be06370b8f))
+* suspend subscription and add API docs ([7952464](https://github.com/Chinlinlee/raccoon-dicom/commit/7952464b4e64ebf2322410fc24d439cd481db5ec))
+* throw error when query key invalid (+1 squashed commits) ([017c18e](https://github.com/Chinlinlee/raccoon-dicom/commit/017c18e674e4dff604fdea8acd9579b214e99d92))
+* **UPS-RS:** add create and get workitem APIs ([4d55216](https://github.com/Chinlinlee/raccoon-dicom/commit/4d552160d591d8f79af3315ea8765816a265006a))
+* use cors only in dev NODE_ENV ([c19d8b0](https://github.com/Chinlinlee/raccoon-dicom/commit/c19d8b0b1183ce691ddcf8abddfbf12d71a7ab1b))
+
+
+### Bug Fixes
+
+* [#6](https://github.com/Chinlinlee/raccoon-dicom/issues/6) ([9f1948a](https://github.com/Chinlinlee/raccoon-dicom/commit/9f1948a07a3109363c8c89abdadd0b11292709cb))
+* cannot cmove study just with UID ([1d8f7bb](https://github.com/Chinlinlee/raccoon-dicom/commit/1d8f7bb33b36523841163fc1663b167b98db42e2))
+* incorrect tag for transaction uid ([8137620](https://github.com/Chinlinlee/raccoon-dicom/commit/81376208de0a764efa9703d1f202ef4fa38dc58c))
+* invalid `subscribed` field in query matching ([e904b7c](https://github.com/Chinlinlee/raccoon-dicom/commit/e904b7cd6f5e9c2dd03e88d03fbfa6339f128f86))
+* joi validator return wrong value ([8dd66f3](https://github.com/Chinlinlee/raccoon-dicom/commit/8dd66f3e75eb9baaa2fac74baf0b5dc29dcb981b))
+* missing patient attribute in workitem ([f778760](https://github.com/Chinlinlee/raccoon-dicom/commit/f7787608085a34ff1c286ab7767c6588f9154b19))
+* not fire init events when create global sub ([f9fa5bd](https://github.com/Chinlinlee/raccoon-dicom/commit/f9fa5bd81791f7a1ce9ca6cfb670cbab33dc7f50))
+* not parse application/dicom+json to json ([8838616](https://github.com/Chinlinlee/raccoon-dicom/commit/88386168e7135e39da409cf714d6d2a07c011e22))
+* response wrong status when empty result ([20fc53c](https://github.com/Chinlinlee/raccoon-dicom/commit/20fc53c1757ec0c65b3a2bd239b1e812dea3d4ad))
+* should not present transaction UID in query ([3481b62](https://github.com/Chinlinlee/raccoon-dicom/commit/3481b628f8968639b121a9286411c68557205c16))
+* wrong way to do time query ([e270a6c](https://github.com/Chinlinlee/raccoon-dicom/commit/e270a6ce0c46c5659e1b587e1f4c142480314bed))
+
+
+### Build
+
+* default enable DIMSE in .env.template ([636e64f](https://github.com/Chinlinlee/raccoon-dicom/commit/636e64ff3529105585cffef1b7f0463ce33cfd3e))
+* **dimse:** update example config ([61a1904](https://github.com/Chinlinlee/raccoon-dicom/commit/61a190430b998eca91835b49c018113cc1ee4f03))
+* **docker:** install imagemagick ([c5443f4](https://github.com/Chinlinlee/raccoon-dicom/commit/c5443f498cd8a5366c7d4875cf22c13e3a659aa4))
+* **log4js:** default support pm2 ([0155745](https://github.com/Chinlinlee/raccoon-dicom/commit/01557457ce150d716e74125c50bee303ca42cea4))
+* update dcm4che bridge ts classes ([5858126](https://github.com/Chinlinlee/raccoon-dicom/commit/5858126853919c8c130e87991c355226c36f2b9c))
+
+### [1.0.1](https://github.com/Chinlinlee/raccoon-dicom/compare/v1.0.0...v1.0.1) (2023-05-04)
+
+
+### Bug Fixes
+
+* naming incorrect about `stow` ([aa8b074](https://github.com/Chinlinlee/raccoon-dicom/commit/aa8b074233908005994382acef03a90b8bdb854c))
+
+## 1.0.0 (2023-05-04)
+
+
+### Features
+
+* [#2](https://github.com/Chinlinlee/raccoon-dicom/issues/2) ([fa924a9](https://github.com/Chinlinlee/raccoon-dicom/commit/fa924a9253e63345cf641f585d889d5eabbc3977))
+* `Controller` for supporting plugin pre/post ([6248724](https://github.com/Chinlinlee/raccoon-dicom/commit/62487248700aea3aba48ba725023a49c67733d03))
+* add `DicomWebService` to handle common fns ([b636f48](https://github.com/Chinlinlee/raccoon-dicom/commit/b636f4885a9e3f836354d84d3d64e36c537994ab))
+* add `QIDO-RS` of study level ([99a6283](https://github.com/Chinlinlee/raccoon-dicom/commit/99a62839f9ae09077064df5551f3481384808fa9))
+* add `TM` tag's schema ([0ac51f1](https://github.com/Chinlinlee/raccoon-dicom/commit/0ac51f1dbb886ca584a84666b6aa7ee2f1e535e1))
+* add API log for rendered instances frames ([4a76246](https://github.com/Chinlinlee/raccoon-dicom/commit/4a76246592fb9694221539556064f3d9d01c53fb))
+* add API of query for series in a study ([a2eb251](https://github.com/Chinlinlee/raccoon-dicom/commit/a2eb251f2469a7470769d7c92796d13427cc6791))
+* add config to determine whether sync FHIR ([052d8c4](https://github.com/Chinlinlee/raccoon-dicom/commit/052d8c4fac1abcae276b92581f132c94c875c1cf))
+* add content-location header when retrieving ([6179073](https://github.com/Chinlinlee/raccoon-dicom/commit/6179073805b4066ca77268564aff8e78dc601ba6))
+* add custom error ([fd8c654](https://github.com/Chinlinlee/raccoon-dicom/commit/fd8c6549c933e3031faf6108914d2db6074af2d7))
+* add dcm4che tool (uft8 converter, dcm2jpg) ([730e0c0](https://github.com/Chinlinlee/raccoon-dicom/commit/730e0c057c151c7b9e995a6e54d76f8ba309c7a8))
+* add delete API of DICOM hierarchy ([66865bc](https://github.com/Chinlinlee/raccoon-dicom/commit/66865bc472d29369a40dc67ff46d7043012ce282))
+* add general API info log ([3dcd205](https://github.com/Chinlinlee/raccoon-dicom/commit/3dcd2052e88de6a2cb23892424ed3788028b3978))
+* add index for UIDs ([b801f86](https://github.com/Chinlinlee/raccoon-dicom/commit/b801f86d61dfb637371bb557c6007a2ea254cd13))
+* add instance level in a series of QIDO-RS ([baa5d4c](https://github.com/Chinlinlee/raccoon-dicom/commit/baa5d4c62d8bd9b14dcc8520058e49fe743ca6f0))
+* add IS type schema & refactor getVRSchema ([6ad47db](https://github.com/Chinlinlee/raccoon-dicom/commit/6ad47db2734d5ca03f160a8febe05a23f001772b))
+* add local uploader ([387620b](https://github.com/Chinlinlee/raccoon-dicom/commit/387620b34ba9a0c7ee33cfea87a7474d09de7ee4))
+* add log4js `api` logger config ([3ed65f5](https://github.com/Chinlinlee/raccoon-dicom/commit/3ed65f587fbd8111f83a2a88d109b79527fec72d))
+* add mediaStorageUID, ID ([7b77892](https://github.com/Chinlinlee/raccoon-dicom/commit/7b77892c42c25dfa509bd02723f6c9fa7ba1bde8))
+* add method to write buffer to multipart ([87c10fb](https://github.com/Chinlinlee/raccoon-dicom/commit/87c10fb4afd1e0c20cae43338c93f5d7420ba6ee))
+* add new error message of response ([30d9586](https://github.com/Chinlinlee/raccoon-dicom/commit/30d95861633c6c1e6f6ebe44a78eba30a246b336))
+* add new QIDO-RS transations ([70efb15](https://github.com/Chinlinlee/raccoon-dicom/commit/70efb156bf9e65e5c200f0479f25fc6b44eb9804))
+* add Patient schema, and store patient ([939e8a2](https://github.com/Chinlinlee/raccoon-dicom/commit/939e8a2a3ae7c589359b982181d23f96874f42b8))
+* add plugin mechanism ([7e57425](https://github.com/Chinlinlee/raccoon-dicom/commit/7e57425434895ae2f4562a09ae77178ebfc05835))
+* add python logger ([a0fd614](https://github.com/Chinlinlee/raccoon-dicom/commit/a0fd6146ca9bef039155370c5a54378cbabe05b3))
+* add Rendered Instance ([0f40a33](https://github.com/Chinlinlee/raccoon-dicom/commit/0f40a337c7a9a67e578435c1a0b8f7dac7410f01))
+* add Rendered Series ([5d4d09f](https://github.com/Chinlinlee/raccoon-dicom/commit/5d4d09ffa4e793582ef6f7d360eda2b815af0993))
+* add retrieve instance's metadata ([b0f9b09](https://github.com/Chinlinlee/raccoon-dicom/commit/b0f9b09309dc6cc55816ce904f43c62252d07528))
+* add retrieve rendered frames ([ca1d81c](https://github.com/Chinlinlee/raccoon-dicom/commit/ca1d81c768099d4d5cd31c1c9a8479fc14a8f378))
+* add retrieve series's instances metadata ([77aec48](https://github.com/Chinlinlee/raccoon-dicom/commit/77aec48ed25563c1a11a55484b23a785f0c19709))
+* add retrieve study's instances metadata ([5a01d9d](https://github.com/Chinlinlee/raccoon-dicom/commit/5a01d9db8dd3357a9b0abbb57b695a000e7f81ec))
+* add retrieve study's instances of `WADO-RS` ([3c83337](https://github.com/Chinlinlee/raccoon-dicom/commit/3c83337313362c1d9e2d91db5bc72c1f4a6f12eb))
+* add retrieve study's series' instances ([1b0f235](https://github.com/Chinlinlee/raccoon-dicom/commit/1b0f235e047d387bf3868c3ce8d32c12afd1ab20))
+* add retrieve thumbnail APIs ([3bcc342](https://github.com/Chinlinlee/raccoon-dicom/commit/3bcc342169a49a4086cea71b067d12bb5de36505))
+* add swagger-jsdoc to generate openapi ([5b13539](https://github.com/Chinlinlee/raccoon-dicom/commit/5b1353983345c6eeede28844ac51a44a5c222869))
+* add url util function ([4971704](https://github.com/Chinlinlee/raccoon-dicom/commit/4971704cad77ca50b4a77ddb15d2e7511e54182d))
+* append `.dcm` of uploaded file ([a76eebf](https://github.com/Chinlinlee/raccoon-dicom/commit/a76eebfbdaa3baf702d171a5bc647a5c7bc122be))
+* change method of query sutdy ([9e3c0fd](https://github.com/Chinlinlee/raccoon-dicom/commit/9e3c0fd7fdce43a5fb543a57a5b25735ba2f28be))
+* config class to handle dotenv file ([ce47a4c](https://github.com/Chinlinlee/raccoon-dicom/commit/ce47a4c2be533b2e21a261c8c38b1946c4592475))
+* create FHIR resource when doing `STOW-RS` ([b381036](https://github.com/Chinlinlee/raccoon-dicom/commit/b381036c8a3ec6c5a431a8094faecf404d7e4f45))
+* **dcm2json:** correct DICOM when missing charset ([ad1f4d1](https://github.com/Chinlinlee/raccoon-dicom/commit/ad1f4d1c62ffbd20cbc575a01898c9cb5709b297))
+* dcm4che QRSCP ([75236be](https://github.com/Chinlinlee/raccoon-dicom/commit/75236be29338409c5ec50dc8e7034c6cf84b20b4))
+* emit background event status in `STOW-RS` ([3baf758](https://github.com/Chinlinlee/raccoon-dicom/commit/3baf758c6edb1cd5dac74db3abf85c33985ab9df))
+* flexible QIDO-RS 00081190 ([18988e5](https://github.com/Chinlinlee/raccoon-dicom/commit/18988e580472db8e0bacc204a2a939f79ceb6ca7))
+* generate API doc in docs/swagger folder ([d8ac79e](https://github.com/Chinlinlee/raccoon-dicom/commit/d8ac79e3224c278033d557b605a7da82a2c13423))
+* generate jpeg of DICOM file when `STOW-RS` ([51fbd36](https://github.com/Chinlinlee/raccoon-dicom/commit/51fbd3613d356df89438fb70ec4a879b121a4e52))
+* get DICOM frame image by dcmtk ([eeb7a76](https://github.com/Chinlinlee/raccoon-dicom/commit/eeb7a7607f996829f8bd80627131653e6fb303e8))
+* get DICOM frame image by python ([d994f88](https://github.com/Chinlinlee/raccoon-dicom/commit/d994f8881de2bfbc1e984eae5b977ddf7a4418eb))
+* get other fields in `getInstanceFrameObj` ([af5d59d](https://github.com/Chinlinlee/raccoon-dicom/commit/af5d59d9690920cb67959daa5ad57e16e5d40e81))
+* get{level}DicomJson for storing into mongo ([7ceb1bb](https://github.com/Chinlinlee/raccoon-dicom/commit/7ceb1bb016320bad503107f5e13f4e26e953cd26))
+* log processing of syncing DIOCM FHIR ([e549625](https://github.com/Chinlinlee/raccoon-dicom/commit/e54962511a18195c2023cf6ff4d725fa7c99db50))
+* make dcm2jpg to static object ([215072c](https://github.com/Chinlinlee/raccoon-dicom/commit/215072c4c660d6313b63dd0a949832e4c78c9c61))
+* multiple processes fn to one pipeline fn ([8e9e41d](https://github.com/Chinlinlee/raccoon-dicom/commit/8e9e41d93febfeb872c79c02ea286d14c3b52efd))
+* QIDO-RS patients ([ea2bffd](https://github.com/Chinlinlee/raccoon-dicom/commit/ea2bffdb5ebdc32942bb84cec86f68906470ff6d))
+* remove code about dcmtk and python ([ae5ceac](https://github.com/Chinlinlee/raccoon-dicom/commit/ae5ceac6bd28b2fd7c69df98d4921bf4ab311473)), closes [#2](https://github.com/Chinlinlee/raccoon-dicom/issues/2)
+* remove user feature ([c494d36](https://github.com/Chinlinlee/raccoon-dicom/commit/c494d367c62180d89e164312ce7bcb534fac9640))
+* replace `00101000` to `00101002` ([4469096](https://github.com/Chinlinlee/raccoon-dicom/commit/4469096bc2ce25f538ecb68c97666d2e25fd2e9e))
+* response dicom multipart when `*` accept ([659c953](https://github.com/Chinlinlee/raccoon-dicom/commit/659c953a2a04edb015f7e30039145f2c04f23f2c))
+* response not found when get inexist DICOM ([dcd90ca](https://github.com/Chinlinlee/raccoon-dicom/commit/dcd90ca643e1c1e38111e2519ae5916887f4bc2b))
+* retrieve instance and move response method ([616cde2](https://github.com/Chinlinlee/raccoon-dicom/commit/616cde2258506559ecdabe4683bbbc8cde8535c4))
+* retrieve study's rendered instances ([bf7919d](https://github.com/Chinlinlee/raccoon-dicom/commit/bf7919d825db353096e4eaeee356b7a792e9f79d))
+* seperate dicom schema to 3 schemas ([e7684c1](https://github.com/Chinlinlee/raccoon-dicom/commit/e7684c19fd576dd51e4fa189111be8beafb1f591))
+* store bulk data url instead of binary ([37f94a6](https://github.com/Chinlinlee/raccoon-dicom/commit/37f94a6650328724d8837834c2fc889ed82e8f88))
+* **stow-rs:** calc and store additional tag ([4ae378c](https://github.com/Chinlinlee/raccoon-dicom/commit/4ae378c40e9cc08070d154a87cfb85127f8a04a3))
+* support `?` match any single character ([3845e38](https://github.com/Chinlinlee/raccoon-dicom/commit/3845e389b7df545d118f376831d3dd02f5fc0d50))
+* support `bulkdata` of DICOMweb API ([1dd1a23](https://github.com/Chinlinlee/raccoon-dicom/commit/1dd1a23956e0c4520d6a865b4717df24bfe22e64))
+* support `iccprofile` ([1a762b0](https://github.com/Chinlinlee/raccoon-dicom/commit/1a762b0240671d21c99b1a7cf7e8bb30905af772))
+* support `includefield` ([f23606d](https://github.com/Chinlinlee/raccoon-dicom/commit/f23606d65d01ee63adfec110e1d4db47206ec6e0)), closes [/dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_8](https://github.com/Chinlinlee//dicom.nema.org/medical/dicom/current/output/html/part18.html/issues/sect_8)
+* support comma-separated frame numbers ([5cf3e73](https://github.com/Chinlinlee/raccoon-dicom/commit/5cf3e736c994289cdd7bb6d93aafc0d6f3f1147a))
+* support log RegExp ([5677940](https://github.com/Chinlinlee/raccoon-dicom/commit/56779400925429c01e742b36a26cfbfa4fd5a09c))
+* support PN query ([61118d0](https://github.com/Chinlinlee/raccoon-dicom/commit/61118d04940fad83dd4eaa95dbd88574ade2ae3d))
+* support WADO-URI ([6f29b12](https://github.com/Chinlinlee/raccoon-dicom/commit/6f29b12c95732dee0f12a8eef5cfff546629ffd2))
+* update `dicom` schema ([2a5f1e5](https://github.com/Chinlinlee/raccoon-dicom/commit/2a5f1e56550ab4c36e5ba68be9526dc999569175))
+* update dicom dictionary ([7d296da](https://github.com/Chinlinlee/raccoon-dicom/commit/7d296da7aed3086cb094ff2282de107c7f4a404b))
+* update DICOM elements dictionary ([b5a3c7d](https://github.com/Chinlinlee/raccoon-dicom/commit/b5a3c7db9ffb98c770a44eab0c53d68778de8491))
+* update store tag of study ([d95be91](https://github.com/Chinlinlee/raccoon-dicom/commit/d95be916f7b083964c1c83879fe1246744973732))
+* update store tags of series ([bcee96a](https://github.com/Chinlinlee/raccoon-dicom/commit/bcee96a407b95be17c356d74a9d401048e4bdb27))
+* use `cors()` instead of hardcode ([46dfb42](https://github.com/Chinlinlee/raccoon-dicom/commit/46dfb42637447dd6b77a6495dc92d094b716ec5e))
+* use `getBasicURL` instead of duplicate code ([d6e400e](https://github.com/Chinlinlee/raccoon-dicom/commit/d6e400e9ad1898068437d7dae7aeaa1c9e1afa71))
+* Use api logger appender instead of ApiLogger ([84f3621](https://github.com/Chinlinlee/raccoon-dicom/commit/84f3621680d2e2bdb068748e22ab4ed1bc760fd1)), closes [#1](https://github.com/Chinlinlee/raccoon-dicom/issues/1)
+* use custom dcm2jpg wrapper ([fcdaedc](https://github.com/Chinlinlee/raccoon-dicom/commit/fcdaedc459bc806b8470c5a9b0813e6736cb9fbf))
+* use express instead of polka ([2e8dc0e](https://github.com/Chinlinlee/raccoon-dicom/commit/2e8dc0ea9f9c877442bf7b0ee7ebf7ad6c401722))
+* use reject instead of resolve false ([95e70d6](https://github.com/Chinlinlee/raccoon-dicom/commit/95e70d6e00023232bc2babe149b052d7159ed979))
+* validator can validate by custom joi object ([49ce0d6](https://github.com/Chinlinlee/raccoon-dicom/commit/49ce0d66c184e9b02db5f2dd2facd902e2623a6c))
+* **wado-rs:** send `content-length` of `zip` ([281927f](https://github.com/Chinlinlee/raccoon-dicom/commit/281927fbf9ef1d6b55df884fc351cf863d1a8faf))
+* **wado-uri:** [#5](https://github.com/Chinlinlee/raccoon-dicom/issues/5) ([a877cd4](https://github.com/Chinlinlee/raccoon-dicom/commit/a877cd49d7dbf802862597f4bf2203d58efc2055))
+
+
+### Bug Fixes
+
+* .env not load when start out of project path ([03834ce](https://github.com/Chinlinlee/raccoon-dicom/commit/03834ce2ca532ec501ba3631a8e2f66ba680ec38))
+* `ENV` env variable is not exists ([f80143c](https://github.com/Chinlinlee/raccoon-dicom/commit/f80143c3b0836e844fecd93758613f8d13b1594c))
+* `initQuery_` should after get limit and skip ([48bcb0d](https://github.com/Chinlinlee/raccoon-dicom/commit/48bcb0d289fe30c74d4c949440dc87b8674eca67))
+* add eslint and run it ([96ce2aa](https://github.com/Chinlinlee/raccoon-dicom/commit/96ce2aa9bf5b39b6eb7097f56a1f3028255e7e08))
+* BulkDataURI's UIDs undefined ([e0602a9](https://github.com/Chinlinlee/raccoon-dicom/commit/e0602a965ac26ef8feef477852f48f3b963e83e8))
+* cannot store value of VR `SQ` ([cf44360](https://github.com/Chinlinlee/raccoon-dicom/commit/cf443603c9d6266eac5bd29c710f870c2f629434))
+* cmove incorrect in study and instance level ([27ec8e0](https://github.com/Chinlinlee/raccoon-dicom/commit/27ec8e0ad42990d3f14caf82f94ee3ffdae330db))
+* DICOM dictionary.dicom is undefined ([fbc96d7](https://github.com/Chinlinlee/raccoon-dicom/commit/fbc96d7155b7d42f37aad0d72e28d7f7b32d58ae))
+* doc null in post findOneAndUpdate function ([c80d5d0](https://github.com/Chinlinlee/raccoon-dicom/commit/c80d5d0a9e77d13aee9782f725c9aaf9eeeae124))
+* docs.pop().pathList may undefined ([4a428d3](https://github.com/Chinlinlee/raccoon-dicom/commit/4a428d355afa31092b516eaf7374263515aa76a7))
+* forget to replace with `doPipeline` to multiple processes ([fa19b70](https://github.com/Chinlinlee/raccoon-dicom/commit/fa19b7035a977f516e91e757c75b50c85ef9374f))
+* frame number apply in `postProcessFrameImage` ([de2dd1a](https://github.com/Chinlinlee/raccoon-dicom/commit/de2dd1a5fb2605da20a35cb49eb0b48618650dc4))
+* incorrect date value store in DB ([365592f](https://github.com/Chinlinlee/raccoon-dicom/commit/365592fa29055da34f6abe2641beaa418a5d5ea3))
+* incorrect property ([5b0eb6c](https://github.com/Chinlinlee/raccoon-dicom/commit/5b0eb6c512de48467a009ca3e11c2b99497af23c))
+* incorrect property of object ([c85f999](https://github.com/Chinlinlee/raccoon-dicom/commit/c85f9997d8e63950dbdbad86f5568fd5c620ba49))
+* incorrect url value of study-series-instance ([c0d5aba](https://github.com/Chinlinlee/raccoon-dicom/commit/c0d5aba5c7aa954116d63110b0a0973d38ea3ae8))
+* incorrect way to convert 00080061 to FHIR ([6876174](https://github.com/Chinlinlee/raccoon-dicom/commit/68761746e81e7e536a7272f49b0d588b3255813d))
+* incorrect way to validate windowLevel's params ([161362e](https://github.com/Chinlinlee/raccoon-dicom/commit/161362e8042801d900c6088271c12987312ee6b0))
+* invisible character in dicom dictionary ([a73cd86](https://github.com/Chinlinlee/raccoon-dicom/commit/a73cd8697a84694bc281d5d7084948d23e3350a7))
+* missing 404 in retrieve rendered instance ([8e70d79](https://github.com/Chinlinlee/raccoon-dicom/commit/8e70d790c6914e52592e0bb768b3143097516584))
+* missing baseUrl in BulkDataURI ([07d4cad](https://github.com/Chinlinlee/raccoon-dicom/commit/07d4cad8c01fbfe2f3eef0821afbe4aa8d634475))
+* missing end res in retrieve instances ([6b5759b](https://github.com/Chinlinlee/raccoon-dicom/commit/6b5759b7f99de38616dc1a271ebc3c4f9ec60377))
+* missing unset `InlineBinary` ([73a2e24](https://github.com/Chinlinlee/raccoon-dicom/commit/73a2e24305b8456bf8fa4da7dcd5f9e7e3ad4832))
+* mongoose drop the QIDO filter ([d210647](https://github.com/Chinlinlee/raccoon-dicom/commit/d2106470a26e1a44416751d49ad18dca794b0386))
+* not allowed empty routers in plugin ([2fe1fc4](https://github.com/Chinlinlee/raccoon-dicom/commit/2fe1fc4653c7103fd9c75b51df72168e7cbdf2d3))
+* not response 204 when empty matched document ([4d8b4a1](https://github.com/Chinlinlee/raccoon-dicom/commit/4d8b4a18ece1fe75d30ad551087a1a18bf22443e))
+* object present _id in PN VR in MongoDB ([4b73691](https://github.com/Chinlinlee/raccoon-dicom/commit/4b7369195df56cef70966dbc6ec5fb31517642a8))
+* query date of end of day `yyyy-mm-dd` ([40b8ce4](https://github.com/Chinlinlee/raccoon-dicom/commit/40b8ce47abdbd0217919bebdbc21a43e18bc8a8e))
+* store incorrect instance path on linux ([807ff60](https://github.com/Chinlinlee/raccoon-dicom/commit/807ff60d256bcfd40f060d689d01dd4efa3bc50e))
+* study has `__v` field ([4f92965](https://github.com/Chinlinlee/raccoon-dicom/commit/4f92965d7404ed792de591f9a3076a53e5f925cc))
+* study level of exist imagingStudy not update ([0cebc85](https://github.com/Chinlinlee/raccoon-dicom/commit/0cebc858dc218b56d3de5d812df13dd517c9a284))
+* the instance store path is not relative ([c2c671e](https://github.com/Chinlinlee/raccoon-dicom/commit/c2c671e64763aa94d6ca1c0fb013b616cfde5151))
+* the VR of `BulkDataURI` is not `UR` ([d24adff](https://github.com/Chinlinlee/raccoon-dicom/commit/d24adffe0c73bedfb0abe6dc11a83a8d510883c9))
+* typo ([629e221](https://github.com/Chinlinlee/raccoon-dicom/commit/629e22196062fe333bbe303b1c63032c066a3a54))
+* typo ([15ca32f](https://github.com/Chinlinlee/raccoon-dicom/commit/15ca32f926691e138767927055de45ba9603dd88))
+* wado retrieve not exist instance must be 404 ([93596fd](https://github.com/Chinlinlee/raccoon-dicom/commit/93596fdc374b6b1182040445c3c2bd4de038e922)), closes [/dicom.nema.org/medical/dicom/current/output/html/part18.html#table_9](https://github.com/Chinlinlee//dicom.nema.org/medical/dicom/current/output/html/part18.html/issues/table_9) [/dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10](https://github.com/Chinlinlee//dicom.nema.org/medical/dicom/current/output/html/part18.html/issues/table_10)
+* wrong `00081190` in `instance` level ([a3244e4](https://github.com/Chinlinlee/raccoon-dicom/commit/a3244e48076f89ab7e65e24941c2c3745dc4fab9))
+
+
+### Build
+
+* add `dicom.dic` for dcmtk ([d2cb179](https://github.com/Chinlinlee/raccoon-dicom/commit/d2cb179f8908b0e93bab5fbea586f4ebe31713ef))
+* add `FHIRSERVER_BASE_URL` in dotenv ([638d35a](https://github.com/Chinlinlee/raccoon-dicom/commit/638d35aa338755fedd1cdf4ff30cd631e8a18c48))
+* add test workflows ([c62b6d2](https://github.com/Chinlinlee/raccoon-dicom/commit/c62b6d2e18cf6a64699e4ceb18d7e12c989547df))
+* **ci:** add function test ([410728d](https://github.com/Chinlinlee/raccoon-dicom/commit/410728de016559bf06860bb3b14bb0201904e75d))
+* **ci:** disable dimse ([52519bf](https://github.com/Chinlinlee/raccoon-dicom/commit/52519bf45d771e8e9af12fcfd74ef88b4ff39d04))
+* **ci:** fix cp library path of dcm4che ([be40d11](https://github.com/Chinlinlee/raccoon-dicom/commit/be40d11f0188736ce5109fdccf980b96943d59fb))
+* **ci:** fix libssl not found ([82dd916](https://github.com/Chinlinlee/raccoon-dicom/commit/82dd91639ae1db77795b01b88b7a5a46441807f4))
+* **ci:** fix missing create .env ([f9d6e88](https://github.com/Chinlinlee/raccoon-dicom/commit/f9d6e88322e556c236b45e0ebeeeb93b38e53341))
+* **ci:** fix permission denied of DICOM root path ([f31b01d](https://github.com/Chinlinlee/raccoon-dicom/commit/f31b01d82d270e635f8cffde571bdfdf3cc32eba))
+* **ci:** missing echo to file ([9eab50c](https://github.com/Chinlinlee/raccoon-dicom/commit/9eab50c0dc52f15989da2d6213f0afafd8eb252f))
+* **ci:** standard version ([f027d0b](https://github.com/Chinlinlee/raccoon-dicom/commit/f027d0b9eb9443d92bc0cb3a46a26dbaec0c8198))
+* fix missing `}` ([7805e59](https://github.com/Chinlinlee/raccoon-dicom/commit/7805e594481bb656541709e0333ee24320356c64))
+* fix token name ([51a01e5](https://github.com/Chinlinlee/raccoon-dicom/commit/51a01e51ce6a45db9e807c2836a8726586969e77))
+* linux-x86-64, windows-x86-64 dcm4che's lib ([9ffb814](https://github.com/Chinlinlee/raccoon-dicom/commit/9ffb81441a73d5b10c34bd11156a170c4823ab6b))
+* **pm2:** increase `max_memory_restart` to 4G ([fcb8cf5](https://github.com/Chinlinlee/raccoon-dicom/commit/fcb8cf56419bba7f9ddaa3ad2d030cdf4dc777ce))
+* remove [@main](https://github.com/main) in raccoon-test ([7fca491](https://github.com/Chinlinlee/raccoon-dicom/commit/7fca49104b15774fa723b92aee8440cd3688510a))
+* remove test ([8b22238](https://github.com/Chinlinlee/raccoon-dicom/commit/8b2223883500408f3b5b55a5a156be7775a9ede1))
+* rename token to repo_token ([2b480a1](https://github.com/Chinlinlee/raccoon-dicom/commit/2b480a11b90bdd248390829b016d0d7305272e67))
+* specific test to main branch ([b0054e2](https://github.com/Chinlinlee/raccoon-dicom/commit/b0054e27c373c5412dceebdf371c8d46f28cc86e))
+* update .env.template ([7ed9be7](https://github.com/Chinlinlee/raccoon-dicom/commit/7ed9be713f52de283c70badc8b614e04ba6eeb2f))
+* update .env.template ([5bf6936](https://github.com/Chinlinlee/raccoon-dicom/commit/5bf69365bb77e136cea4431afd4fe68064959baf))
+* update .env.template ([c65f99c](https://github.com/Chinlinlee/raccoon-dicom/commit/c65f99cfd7faa590f8ab40f61fe739d224e48df0))
+* update `Dockerfile` and add docker-compose ([a936d00](https://github.com/Chinlinlee/raccoon-dicom/commit/a936d0010e0e4b9dd98b82862b279e286a85e310))
+* use PAT instead of GITHUB_TOKEN ([58cef54](https://github.com/Chinlinlee/raccoon-dicom/commit/58cef54d20f3572531eb871d9e7f3bbd4af10fbe))
diff --git a/README.md b/README.md
index dcf5f73c..260be948 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,72 @@
-# raccoon-only-dicom
-Another raccoon focus on dicom. The original raccoon combine the FHIR and DICOM together in MongoDB, I think it will cause performance and maintenance issues. So, here start a new project only for DICOM.
+# raccoon-dicom
+
+
+
+
+
+
+Another Raccoon focus on DICOM.
+
+[English](README.md) | [繁體中文](README.zh-TW.md)
+
+---
+
+**Raccoon-DICOM** is a noSQL-based medical image archive designed for managing DICOM images, utilizing MongoDB to store and manage the images while providing RESTful APIs that support [DICOMweb](https://www.dicomstandard.org/dicomweb/") protocols for querying, retrieving, and managing DICOM images.
+
+# Installation
+- [Installation](https://github.com/Chinlinlee/raccoon-dicom/wiki/Installation)
+- Step by Step guide to installing Raccoon-DICOM - Windows (WIP🚧)
+- Step by Step guide to installing Raccoon-DICOM - Ubuntu (WIP🚧)
+
+# Troubleshooting on linux
+- `Unknown VR: Tag not found in data dictionary` when using `STOW-RS`
+ - You need set the `DCMDICTPATH` environment variable
+ - The `dicom.dic` can find in the `/usr/share/libdcmtk{version}` or `./models/DICOM/dcmtk/dicom.dic`
+ > The {version} corresponds to dcmtk version, e.g. 3.6.5 => libdcmtk15
+
+ - Set `DCMDICTPATH` environment variable using command or you can add the command to profile file(`~/.bashrc`,`~/.profile` etc.), example **with dcmtk 3.6.5**:
+ ```sh
+ export DCMDICTPATH=/usr/share/libdcmtk15/dicom.dic
+ ```
+ - Check the environment variable
+ ```sh
+ echo $DCMDICTPATH
+ ```
# Features
The features implemented here:
-- [QIDO-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.6)
-- [STOW-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.5)
- - Convert DICOM to ImagingStudy, Endpoint, Patient of FHIR resources and sync FHIR resources to FHIR server
-- [WADO-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.4.1.1.1)
- - [Retrieve Transaction Instance Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-1)
- - [Retrieve Transaction Metadata Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-2)
- - [Retrieve Transaction Rendered Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-3)
- - [Retrieve Transaction Bulkdata Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1.5-1)
-
-# Environment Requirements
-- node.js >= 16
-- Java JDK >= 11
-
-> **Note**
-> - You should copy opencv_java library to JDK's lib directory
-> - In windows, copy `opencv_java.dll`
-> - In linux, copy `libclib_jiio.so` and `libopencv_java.so`
+## [QIDO-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.6)
+### Support Format (Media Types)
+
+Format | Support |
+---------|----------|
+ application/dicom+json | ✅ |
+ multipart/related; type="application/dicom+xml | ❌ |
+
+### Support Query Parameter
+
+Query Parameter | Support |
+---------|----------|
+ fuzzymatching | ❌ |
+ includefield | ✅ |
+ limit | ✅ |
+ offset | ✅ |
+
+
+## [STOW-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.5)
+- You can set `SYCN_TO_FHIR_SERVER=true` in .env to convert DICOM to ImagingStudy, Endpoint, Patient of FHIR resources and sync FHIR resources to FHIR server
+## [WADO-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.4.1.1.1)
+- [Retrieve Transaction Instance Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-1)
+- [Retrieve Transaction Metadata Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-2)
+- [Retrieve Transaction Rendered Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-3)
+- [Retrieve Transaction Thumbnail Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-4)
+- [Retrieve Transaction Bulkdata Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1.5-1)
+
# API Documentation
- raccoon-dicom uses swagger ui hosting openapi.json to generate documentation
- [API Documentation](https://chinlinlee.github.io/raccoon-dicom/)
-
+# Wiki
+Our [wiki](https://github.com/Chinlinlee/raccoon-dicom/wiki) includes a lot of information about raccoon-dicom, we heavily encourage you to take a look!!
diff --git a/README.zh-TW.md b/README.zh-TW.md
new file mode 100644
index 00000000..6d1454e0
--- /dev/null
+++ b/README.zh-TW.md
@@ -0,0 +1,72 @@
+# raccoon-dicom
+
+
+
+
+
+
+Another Raccoon focus on DICOM.
+
+[English](README.md) | [繁體中文](README.zh-TW.md)
+
+---
+
+**Raccoon-DICOM** 是使用 no-SQL 資料庫實作的醫學影像儲存系統(DICOMweb PACS),其使用 MongoDB 管理 DICOM 影像並提供 [DICOMweb](https://www.dicomstandard.org/dicomweb/") RESTful API 功能進行儲存、查詢以及調閱
+
+
+# 安裝
+- [安裝手冊](https://github.com/Chinlinlee/raccoon-dicom/wiki/Installation.zh-TW)
+- [從 0 開始部屬 Raccoon - Windows](https://github.com/Chinlinlee/raccoon-dicom/wiki/From-zero-to-deploy.zh-TW): 在 Windows 上,一步一步從安裝到部屬
+- 從 0 開始部屬 Raccoon - Ubuntu (WIP🚧)
+
+# Troubleshooting on linux
+- `Unknown VR: Tag not found in data dictionary` when using `STOW-RS`
+ - 您必須設定 `DCMDICTPATH` 環境變數
+ - `dicom.dic` 檔案可以在`/usr/share/libdcmtk{version}`或 `./models/DICOM/dcmtk/dicom.dic`找到
+ > {version} 對應到dcmtk的版本, e.g. 3.6.5 => libdcmtk15
+
+ - 使用指令設定 `DCMDICTPATH` 或者您可以將指令加入到profile檔案中(`~/.bashrc`,`~/.profile` etc.), example **with dcmtk 3.6.5**:
+ ```sh
+ export DCMDICTPATH=/usr/share/libdcmtk15/dicom.dic
+ ```
+ - 檢查環境變數
+ ```sh
+ echo $DCMDICTPATH
+ ```
+
+# 提供之功能
+目前以實作的功能如下:
+## [QIDO-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.6)
+### 支援的格式 (Media Types)
+
+Format | Support |
+---------|----------|
+ application/dicom+json | ✅ |
+ multipart/related; type="application/dicom+xml | ❌ |
+
+### 支援的查詢參數
+
+Query Parameter | Support |
+---------|----------|
+ fuzzymatching | ❌ |
+ includefield | ✅ |
+ limit | ✅ |
+ offset | ✅ |
+
+
+## [STOW-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.5)
+- 您可以在 .env 設定 `SYCN_TO_FHIR_SERVER=true` 以將 DICOM 轉換為 FHIR ImagingStudy, Endpoint 以及 Patient,並同步這些 Resources 至 FHIR server
+## [WADO-RS](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.4.1.1.1)
+- [Retrieve Transaction Instance Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-1)
+- [Retrieve Transaction Metadata Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-2)
+- [Retrieve Transaction Rendered Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-3)
+- [Retrieve Transaction Thumbnail Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1-4)
+- [Retrieve Transaction Bulkdata Resources](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_10.4.1.5-1)
+
+
+# API Documentation
+- raccoon-dicom uses swagger ui hosting openapi.json to generate documentation
+- [API Documentation](https://chinlinlee.github.io/raccoon-dicom/)
+
+# Wiki
+我們的[Wiki](https://github.com/Chinlinlee/raccoon-dicom/wiki)含有更多與 Raccoon-DICOM 更多的資訊,非常建議您閱讀一下
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..681070aa
--- /dev/null
+++ b/api-sql/WADO-URI/service/WADO-URI.service.js
@@ -0,0 +1,99 @@
+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
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-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-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..144cbd57
--- /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-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-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..f6bf5fac
--- /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/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..fdd27330
--- /dev/null
+++ b/api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js
@@ -0,0 +1,17 @@
+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
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/QIDO-RS/service/instanceQueryBuilder.js b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js
new file mode 100644
index 00000000..655ce353
--- /dev/null
+++ b/api-sql/dicom-web/controller/QIDO-RS/service/instanceQueryBuilder.js
@@ -0,0 +1,435 @@
+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
new file mode 100644
index 00000000..96d962aa
--- /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");
+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
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-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..f4222271
--- /dev/null
+++ b/api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js
@@ -0,0 +1,495 @@
+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
new file mode 100644
index 00000000..779a7692
--- /dev/null
+++ b/api-sql/dicom-web/controller/QIDO-RS/service/seriesQueryBuilder.js
@@ -0,0 +1,209 @@
+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
new file mode 100644
index 00000000..915915b1
--- /dev/null
+++ b/api-sql/dicom-web/controller/UPS-RS/service/base-workItem.service.js
@@ -0,0 +1,76 @@
+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
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-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..3895cdb9
--- /dev/null
+++ b/api-sql/dicom-web/controller/UPS-RS/service/change-workItem-state.service.js
@@ -0,0 +1,56 @@
+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
new file mode 100644
index 00000000..7a64639a
--- /dev/null
+++ b/api-sql/dicom-web/controller/UPS-RS/service/create-workItem.service.js
@@ -0,0 +1,39 @@
+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
new file mode 100644
index 00000000..87359185
--- /dev/null
+++ b/api-sql/dicom-web/controller/UPS-RS/service/get-workItem.service.js
@@ -0,0 +1,39 @@
+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
new file mode 100644
index 00000000..8fa740ca
--- /dev/null
+++ b/api-sql/dicom-web/controller/UPS-RS/service/query/upsQueryBuilder.js
@@ -0,0 +1,229 @@
+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
new file mode 100644
index 00000000..c5e80c04
--- /dev/null
+++ b/api-sql/dicom-web/controller/UPS-RS/service/subscribe.service.js
@@ -0,0 +1,147 @@
+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
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-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-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..f89389dc
--- /dev/null
+++ b/api-sql/dicom-web/controller/UPS-RS/service/update-workItem.service.js
@@ -0,0 +1,73 @@
+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
new file mode 100644
index 00000000..22a6ec05
--- /dev/null
+++ b/api-sql/dicom-web/controller/WADO-RS/bulkdata/service/bulkdata.js
@@ -0,0 +1,88 @@
+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
new file mode 100644
index 00000000..15476362
--- /dev/null
+++ b/api-sql/dicom-web/controller/WADO-RS/deletion/service/delete.js
@@ -0,0 +1,74 @@
+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
new file mode 100644
index 00000000..4efe492f
--- /dev/null
+++ b/api-sql/dicom-web/controller/WADO-RS/service/WADO-RS.service.js
@@ -0,0 +1,46 @@
+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
new file mode 100644
index 00000000..cdaab2ed
--- /dev/null
+++ b/api-sql/dicom-web/controller/WADO-RS/service/rendered.service.js
@@ -0,0 +1,318 @@
+const {
+ 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");
+
+/**
+ *
+ * @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 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
+ },
+ 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.writeSpecificFramesRenderedImages = writeSpecificFramesRenderedImages;
+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
new file mode 100644
index 00000000..1e635bc4
--- /dev/null
+++ b/api-sql/dicom-web/controller/WADO-RS/service/thumbnail.service.js
@@ -0,0 +1,33 @@
+const renderedService = require("@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/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/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..9836a99a
--- /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("@mwl-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
new file mode 100644
index 00000000..5b7a04aa
--- /dev/null
+++ b/api/dicom-web/controller/MWL-RS/change-mwlItem-status.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 { ChangeMwlItemStatusService } = require("@mwl-service/change-mwlItem-status");
+
+class ChangeMwlItemStatusController 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 ChangeMwlItemStatusController(req, res);
+
+ await controller.doPipeline();
+};
\ 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
new file mode 100644
index 00000000..f64debfa
--- /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("@mwl-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/create-mwlItem.js b/api/dicom-web/controller/MWL-RS/create-mwlItem.js
new file mode 100644
index 00000000..aa387248
--- /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("@mwl-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/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/get-mwlItem.js b/api/dicom-web/controller/MWL-RS/get-mwlItem.js
new file mode 100644
index 00000000..b3065fec
--- /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("@mwl-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/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/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/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..449059be
--- /dev/null
+++ b/api/dicom-web/controller/MWL-RS/service/count-mwlItem.service.js
@@ -0,0 +1,16 @@
+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) {
+ super(req, res);
+ }
+
+ async getMwlItemCount() {
+ this.query = (await convertRequestQueryToMongoQuery(this.query)).$match;
+ return await MwlItemModel.getCount(this.query);
+ }
+}
+
+module.exports.GetMwlItemCountService = GetMwlItemCountService;
\ 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..dd43c762
--- /dev/null
+++ b/api/dicom-web/controller/MWL-RS/service/create-mwlitem.service.js
@@ -0,0 +1,142 @@
+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[0]);
+ 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({
+ [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");
+ }
+
+ let spsIDPath = `${dictionary.keyword.ScheduledProcedureStepSequence}.Value.0.${dictionary.keyword.ScheduledProcedureStepID}`;
+ if (!spsItem.getValue(spsIDPath)) {
+ let spsID = shortHash(uuidV4());
+ spsItem.setValue(spsIDPath, `SPS-${spsID}`);
+ }
+ mwlItem[dictionary.keyword.ScheduledProcedureStepSequence] = {
+ ...mwlItem[dictionary.keyword.ScheduledProcedureStepSequence],
+ ...spsItem.dicomJson[dictionary.keyword.ScheduledProcedureStepSequence]
+ };
+
+ 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({
+ 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 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
+ }
+ ]
+ });
+
+ 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/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..634300ff
--- /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("@dbModels/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/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..e277b18e
--- /dev/null
+++ b/api/dicom-web/controller/MWL-RS/service/get-mwlItem.service.js
@@ -0,0 +1,25 @@
+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) {
+ super(req, res);
+ }
+
+ async getMwlItems() {
+ let query = (await convertRequestQueryToMongoQuery(this.query)).$match;
+ let queryOptions = {
+ 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/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..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,
@@ -69,7 +70,7 @@ class QidoRsService {
if (!query[queryKey]) delete query[queryKey];
}
- this.query = convertAllQueryToDICOMTag(query);
+ this.query = convertAllQueryToDicomTag(query);
}
async getAndResponseDicomJson() {
@@ -134,44 +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) {
- 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
- );
- } else if (newKeyNames.length >= 2) {
- retKeyName = newKeyNames.map(v => v + ".Value").join(".");
- } else {
- newKeyNames.push("Value");
- 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/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/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");
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/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/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/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..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");
+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
});
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..d5384075 100644
--- a/api/dicom-web/controller/UPS-RS/service/cancel.service.js
+++ b/api/dicom-web/controller/UPS-RS/service/cancel.service.js
@@ -1,12 +1,10 @@
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
} = 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");
@@ -25,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") {
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..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,12 +2,12 @@ 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
} = 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 {
@@ -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..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
@@ -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 {
@@ -7,8 +7,8 @@ const {
DicomWebStatusCodes
} = require("@error/dicom-web-service");
const { DicomJsonModel } = require("@dicom-json-model");
-const { BaseWorkItemService } = require("./base-workItem.service");
-const { SubscribeService } = require("./subscribe.service");
+const { BaseWorkItemService } = require("@ups-service/base-workItem.service");
+const { SubscribeService } = require("@ups-service/subscribe.service");
const { UPS_EVENT_TYPE } = require("./workItem-event");
const { dictionary } = require("@models/DICOM/dicom-tags-dic");
@@ -24,6 +24,20 @@ class CreateWorkItemService extends BaseWorkItemService {
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 PatientModel.findOneOrCreatePatient(patientId, this.requestWorkItem.dicomJson);
+ 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",
@@ -41,6 +55,11 @@ class CreateWorkItemService extends BaseWorkItemService {
});
}
+ let patientId = this.requestWorkItem.getString("00100020");
+ _.set(this.requestWorkItem.dicomJson, "patientID", patientId);
+ }
+
+ async validateWorkItem(uid) {
if (this.requestWorkItem.getString("00741000") !== "SCHEDULED") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSNotScheduled,
@@ -49,10 +68,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 +75,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,42 +89,22 @@ 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);
+ 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),
_.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;
- }
-
- async findOneOrCreatePatient() {
- let patientId = this.requestWorkItem.getString("00100020");
- _.set(this.requestWorkItem.dicomJson, "patientID", 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;
+ this.triggerUpsEvents();
}
async isUpsExist(uid) {
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..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
@@ -1,9 +1,9 @@
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");
-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) {
@@ -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..41195066 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 {
@@ -9,9 +9,9 @@ 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");
+const { convertAllQueryToDicomTag } = require("@root/api/dicom-web/service/base-query.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/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 62fac381..c48ca2c2 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 {
@@ -9,8 +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 { convertAllQueryToDICOMTag } = require("../../QIDO-RS/service/QIDO-RS.service");
+const { BaseWorkItemService } = require("@ups-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 b1b33d8e..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
@@ -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 {
@@ -7,28 +7,29 @@ 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");
-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
@@ -57,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);
@@ -162,8 +167,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);
}
}
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");
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");
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");
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/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/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/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/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 {
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/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/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/api/dicom-web/mwl-rs.route.js b/api/dicom-web/mwl-rs.route.js
new file mode 100644
index 00000000..06e0e0dd
--- /dev/null
+++ b/api/dicom-web/mwl-rs.route.js
@@ -0,0 +1,160 @@
+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")
+);
+
+/**
+ * @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"));
+
+/**
+ * @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"));
+
+/**
+ * @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"));
+
+
+/**
+ * @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")
+);
+
+/**
+ * @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
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..3a023e45
--- /dev/null
+++ b/api/dicom-web/service/base-query.service.js
@@ -0,0 +1,100 @@
+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.BaseQueryService = BaseQueryService;
+module.exports.convertAllQueryToDicomTag = convertAllQueryToDicomTag;
\ No newline at end of file
diff --git a/config-class.js b/config-class.js
index 1d3d61d3..c1de7812 100644
--- a/config-class.js
+++ b/config-class.js
@@ -24,6 +24,20 @@ 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() {
this.dbName = env.get("MONGODB_NAME").default("raccoon").asString();
@@ -42,6 +56,7 @@ 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"]);
}
}
@@ -65,19 +80,28 @@ class FhirConfig {
class RaccoonConfig {
constructor() {
- this.mongoDbConfig = new MongoDbConfig();
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.mongoDbConfig.dbName, NAME_SPACE)
+ uuid.v5(this.dbConfig.dbName, NAME_SPACE)
);
/** @type {string} */
- this.mediaStorageID = this.mongoDbConfig.dbName;
+ 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)
diff --git a/config/jsconfig.mongodb.json b/config/jsconfig.mongodb.json
new file mode 100644
index 00000000..199b4dd8
--- /dev/null
+++ b/config/jsconfig.mongodb.json
@@ -0,0 +1,36 @@
+{
+ "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
new file mode 100644
index 00000000..c4d1a146
--- /dev/null
+++ b/config/jsconfig.sql.json
@@ -0,0 +1,36 @@
+{
+ "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
new file mode 100644
index 00000000..1ccf6c60
--- /dev/null
+++ b/config/modula-alias/mongodb/package.json
@@ -0,0 +1,32 @@
+{
+ "_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",
+ "@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"
+ }
+}
\ 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..cf5845c7
--- /dev/null
+++ b/config/modula-alias/sql/package.json
@@ -0,0 +1,32 @@
+{
+ "_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",
+ "@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"
+ }
+}
\ No newline at end of file
diff --git a/dimse-sql/index.js b/dimse-sql/index.js
new file mode 100644
index 00000000..49196011
--- /dev/null
+++ b/dimse-sql/index.js
@@ -0,0 +1,53 @@
+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
new file mode 100644
index 00000000..ec1cf106
--- /dev/null
+++ b/dimse-sql/instanceQueryTask.js
@@ -0,0 +1,90 @@
+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
new file mode 100644
index 00000000..42e4145b
--- /dev/null
+++ b/dimse-sql/patientQueryTask.js
@@ -0,0 +1,83 @@
+const _ = require("lodash");
+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 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
new file mode 100644
index 00000000..abbec7e4
--- /dev/null
+++ b/dimse-sql/queryBuilder.js
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 00000000..bcdcf38d
--- /dev/null
+++ b/dimse-sql/seriesQueryTask.js
@@ -0,0 +1,93 @@
+const _ = require("lodash");
+
+const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject");
+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 { 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
new file mode 100644
index 00000000..79854318
--- /dev/null
+++ b/dimse-sql/studyQueryTask.js
@@ -0,0 +1,105 @@
+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
new file mode 100644
index 00000000..a090c17c
--- /dev/null
+++ b/dimse-sql/utils.js
@@ -0,0 +1,110 @@
+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.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/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 30e24b45..6461da5c 100644
--- a/dimse/c-store.js
+++ b/dimse/c-store.js
@@ -1,10 +1,8 @@
-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");
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");
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);
}
diff --git a/docs/swagger/parameters/dicomweb-common.yaml b/docs/swagger/parameters/dicomweb-common.yaml
index b2c1448b..2175fd21 100644
--- a/docs/swagger/parameters/dicomweb-common.yaml
+++ b/docs/swagger/parameters/dicomweb-common.yaml
@@ -28,4 +28,12 @@ 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}"
+ 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
new file mode 100644
index 00000000..f5459729
--- /dev/null
+++ b/docs/swagger/parameters/mwl.yaml
@@ -0,0 +1,23 @@
+components:
+ parameters:
+ spsID:
+ 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
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) {
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/jsconfig.json b/jsconfig.json
deleted file mode 100644
index 9d8fade8..00000000
--- a/jsconfig.json
+++ /dev/null
@@ -1,19 +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"]
- }
- },
- "exclude": [
- "node_modules", "**/node_modules/*"
- ]
-}
\ No newline at end of file
diff --git a/models/DICOM/audit/auditManager.js b/models/DICOM/audit/auditManager.js
index b89b8596..d50fbcae 100644
--- a/models/DICOM/audit/auditManager.js
+++ b/models/DICOM/audit/auditManager.js
@@ -2,6 +2,10 @@ 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("@models/mongodb/models/auditMessage");
+const { raccoonConfig } = require("@root/config-class");
/**
* @typedef AuditMessageModel
@@ -159,8 +163,9 @@ class AuditManager {
}
static getAuditMessageModel() {
- const mongoose = require("mongoose");
- return mongoose.model("auditMessage");
+ if (raccoonConfig.serverConfig.dbType === "sql")
+ return new AuditMessageModel(new AuditMessageModelLoggerDbImpl());
+ return new AuditMessageModel(AuditMessageModelMongodbDbImpl);
}
}
diff --git a/models/DICOM/audit/auditMessageFactory.js b/models/DICOM/audit/auditMessageFactory.js
index d9a565ea..87612832 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 mongoose = require("mongoose");
- return mongoose.model("dicom");
+ 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/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]);
}
diff --git a/models/DICOM/dicom-tags-mapping.js b/models/DICOM/dicom-tags-mapping.js
index d889d338..273867ec 100644
--- a/models/DICOM/dicom-tags-mapping.js
+++ b/models/DICOM/dicom-tags-mapping.js
@@ -618,5 +618,154 @@ 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"
+ },
+ "00400100": {
+ "vr": "SQ"
+ },
+ "00401001": {
+ "vr": "SH"
+ },
+ "00401003": {
+ "vr": "SH"
+ },
+ "00401004": {
+ "vr": "LO"
+ },
+ "00403001": {
+ "vr": "LO"
+ }
+ }
};
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/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/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js
new file mode 100644
index 00000000..ca1de43c
--- /dev/null
+++ b/models/mongodb/models/mwlitems.model.js
@@ -0,0 +1,102 @@
+const path = require("path");
+const mongoose = require("mongoose");
+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(
+ {},
+ {
+ strict: true,
+ versionKey: false,
+ toObject: {
+ getters: true
+ },
+ statics: {
+ /**
+ *
+ * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions
+ * @returns
+ */
+ getDicomJson: async function (queryOptions) {
+ let projection = mongoose.model("mwlItems").getDicomJsonProjection(queryOptions.includeFields);
+ try {
+ let docs = await mongoose.model("mwlItems").find(queryOptions.query, projection)
+ .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;
+ }
+ },
+ getDicomJsonProjection: function (includeFields) {
+ let includeFieldsFactory = new IncludeFieldsFactory(includeFields);
+ return includeFieldsFactory.getMwlLevelFields();
+ },
+ 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: {
+ 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/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;
}
}
}
diff --git a/models/mongodb/models/workItems.js b/models/mongodb/models/workitems.model.js
similarity index 91%
rename from models/mongodb/models/workItems.js
rename to models/mongodb/models/workitems.model.js
index bea22c29..35a5ebb8 100644
--- a/models/mongodb/models/workItems.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);
+ }
}
}
);
@@ -119,3 +125,4 @@ let workItemModel = mongoose.model(
/** @type { WorkItemsModel } */
module.exports = workItemModel;
+module.exports.WorkItemModel = workItemModel;
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
*/
diff --git a/models/sql/deleteSchedule.js b/models/sql/deleteSchedule.js
new file mode 100644
index 00000000..81cadb73
--- /dev/null
+++ b/models/sql/deleteSchedule.js
@@ -0,0 +1,113 @@
+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("0 0 */1 * * *", 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();
+ 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
new file mode 100644
index 00000000..4fd110ea
--- /dev/null
+++ b/models/sql/dicom-json-model.js
@@ -0,0 +1,94 @@
+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
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/models/sql/init.js b/models/sql/init.js
new file mode 100644
index 00000000..819276df
--- /dev/null
+++ b/models/sql/init.js
@@ -0,0 +1,213 @@
+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
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/models/sql/instance.js b/models/sql/instance.js
new file mode 100644
index 00000000..e71e1d32
--- /dev/null
+++ b/models/sql/instance.js
@@ -0,0 +1,9 @@
+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/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;
diff --git a/models/sql/models/dicomCode.model.js b/models/sql/models/dicomCode.model.js
new file mode 100644
index 00000000..4efb9c8e
--- /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.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
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/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;
diff --git a/models/sql/models/instance.model.js b/models/sql/models/instance.model.js
new file mode 100644
index 00000000..251bf99f
--- /dev/null
+++ b/models/sql/models/instance.model.js
@@ -0,0 +1,257 @@
+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");
+
+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();
+ }
+
+ 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;
+};
+
+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/mwlitems.model.js b/models/sql/models/mwlitems.model.js
new file mode 100644
index 00000000..8757971d
--- /dev/null
+++ b/models/sql/models/mwlitems.model.js
@@ -0,0 +1,195 @@
+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
new file mode 100644
index 00000000..a262a867
--- /dev/null
+++ b/models/sql/models/patient.model.js
@@ -0,0 +1,92 @@
+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");
+
+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 {
+ 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
+ }
+}, {
+ 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;
+ }));
+};
+
+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/personName.model.js b/models/sql/models/personName.model.js
new file mode 100644
index 00000000..9e0be69b
--- /dev/null
+++ b/models/sql/models/personName.model.js
@@ -0,0 +1,94 @@
+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
new file mode 100644
index 00000000..fcb74252
--- /dev/null
+++ b/models/sql/models/series.model.js
@@ -0,0 +1,185 @@
+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");
+
+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");
+ }
+
+ 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(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;
+ }
+};
+
+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/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/models/study.model.js b/models/sql/models/study.model.js
new file mode 100644
index 00000000..62c623d1
--- /dev/null
+++ b/models/sql/models/study.model.js
@@ -0,0 +1,228 @@
+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");
+
+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({
+ 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 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(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;
+ }
+};
+
+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/models/sql/models/upsGlobalSubscription.model.js b/models/sql/models/upsGlobalSubscription.model.js
new file mode 100644
index 00000000..f391f062
--- /dev/null
+++ b/models/sql/models/upsGlobalSubscription.model.js
@@ -0,0 +1,56 @@
+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
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/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;
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
diff --git a/models/sql/models/workitems.model.js b/models/sql/models/workitems.model.js
new file mode 100644
index 00000000..c462b0a8
--- /dev/null
+++ b/models/sql/models/workitems.model.js
@@ -0,0 +1,204 @@
+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
new file mode 100644
index 00000000..2609e969
--- /dev/null
+++ b/models/sql/po/instance.po.js
@@ -0,0 +1,512 @@
+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
new file mode 100644
index 00000000..c377429a
--- /dev/null
+++ b/models/sql/po/mwlItem.po.js
@@ -0,0 +1,164 @@
+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
new file mode 100644
index 00000000..67412da0
--- /dev/null
+++ b/models/sql/po/patient.po.js
@@ -0,0 +1,80 @@
+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
new file mode 100644
index 00000000..53b8a127
--- /dev/null
+++ b/models/sql/po/series.po.js
@@ -0,0 +1,186 @@
+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
new file mode 100644
index 00000000..ef9f050a
--- /dev/null
+++ b/models/sql/po/study.po.js
@@ -0,0 +1,84 @@
+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
new file mode 100644
index 00000000..928f268f
--- /dev/null
+++ b/models/sql/po/upsWorkItem.po.js
@@ -0,0 +1,222 @@
+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
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
diff --git a/models/sql/vrTypeMapping.js b/models/sql/vrTypeMapping.js
new file mode 100644
index 00000000..9ecd7349
--- /dev/null
+++ b/models/sql/vrTypeMapping.js
@@ -0,0 +1,40 @@
+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
diff --git a/package-lock.json b/package-lock.json
index cd354ef9..f41e40f6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "raccoon-only-dicom",
- "version": "1.0.0",
+ "version": "1.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "raccoon-only-dicom",
- "version": "1.0.0",
+ "version": "1.2.0",
"license": "MIT",
"dependencies": {
"@jorgeferrero/stream-to-buffer": "^2.0.6",
@@ -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",
@@ -44,10 +45,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",
@@ -58,6 +62,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"
}
@@ -831,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",
@@ -1411,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",
@@ -1423,6 +1525,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",
@@ -1434,6 +1541,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",
@@ -1945,6 +2057,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",
@@ -2587,6 +2707,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",
@@ -3271,6 +3405,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",
@@ -3306,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",
@@ -3926,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"
},
@@ -4564,6 +4708,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",
@@ -4722,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"
@@ -4738,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"
],
@@ -4799,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"
],
@@ -4814,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"
],
@@ -4829,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"
],
@@ -4844,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"
],
@@ -4859,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"
],
@@ -4882,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"
@@ -4904,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"
},
@@ -4918,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": {
@@ -6420,6 +6545,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",
@@ -6569,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"
@@ -6584,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": {
@@ -6645,6 +6775,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",
@@ -6678,6 +6910,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",
@@ -7231,6 +7498,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",
@@ -7350,6 +7622,102 @@
"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-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",
+ "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",
@@ -7886,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",
@@ -7904,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",
@@ -8133,6 +8527,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",
@@ -8249,6 +8648,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",
@@ -8367,7 +8771,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"
}
@@ -8424,6 +8827,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.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -8458,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",
@@ -8530,7 +8958,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"
}
@@ -9405,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",
@@ -9879,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",
@@ -9891,6 +10384,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",
@@ -9902,6 +10400,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",
@@ -10309,6 +10812,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",
@@ -10839,6 +11347,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",
@@ -11352,6 +11868,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",
@@ -11389,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",
@@ -11868,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=="
}
}
},
@@ -12341,6 +12867,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",
@@ -12466,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": {
@@ -12519,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": {
@@ -13726,6 +14234,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",
@@ -13840,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=="
}
}
},
@@ -13899,6 +14412,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",
@@ -13920,6 +14510,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",
@@ -14356,6 +14969,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",
@@ -14443,6 +15061,59 @@
}
}
},
+ "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-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",
+ "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",
@@ -14849,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",
@@ -14863,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",
@@ -15047,6 +15736,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",
@@ -15130,6 +15824,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",
@@ -15237,8 +15936,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",
@@ -15280,6 +15978,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.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -15341,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",
@@ -15355,8 +16071,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 20a14d0e..bfef97f7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "raccoon-only-dicom",
- "version": "1.0.0",
+ "version": "1.2.0",
"description": "",
"main": "index.js",
"scripts": {
@@ -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/mongodb/models",
- "@dbInitializer": "./models/mongodb/index.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"
- },
"dependencies": {
"@jorgeferrero/stream-to-buffer": "^2.0.6",
"archiver": "^5.3.1",
@@ -76,6 +64,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",
@@ -102,10 +91,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",
@@ -116,6 +108,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"
}
diff --git a/plugins/README.md b/plugins/README.md
new file mode 100644
index 00000000..3a91ad40
--- /dev/null
+++ b/plugins/README.md
@@ -0,0 +1,10 @@
+# Plugins Configuration
+The every plugin(middleware) must have a properties listed below:
+
+property name | type | description
+---------|----------|---------
+ enable | boolean | enabled/disable the plugin
+ before | boolean | Use middleware before/after the router
+ routers | object[] | Array of routers that you want to add middleware
+ routers.path | string | The path of the router that you want to add middleware
+ routers.method | string | The API method of this router
\ No newline at end of file
diff --git a/routes.js b/routes.js
index a994920c..60587fb4 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/mwl-rs.route"));
app.use("/wado", require("./api/WADO-URI"));
};
diff --git a/server.js b/server.js
index 0fb6fc1b..cc601547 100644
--- a/server.js
+++ b/server.js
@@ -1,5 +1,11 @@
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");
@@ -8,12 +14,30 @@ 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");
+
+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 { raccoonConfig } = require("./config-class");
-const { DcmQrScp } = require('./dimse');
+const { DcmQrScp } = require('@dimse');
require("dotenv");
require("./websocket");
@@ -42,6 +66,7 @@ app.use(
//#region session
+
app.use(
session({
secret: raccoonConfig.serverConfig.secretKey || "secretKey",
@@ -51,10 +76,7 @@ app.use(
httpOnly: true,
maxAge: 60 * 60 * 1000
},
- store: MongoStore.create({
- client: mongoose.connection.getClient(),
- dbName: raccoonConfig.mongoDbConfig.dbName
- })
+ store: sessionStoreOption
})
);
diff --git a/test/QIDO-RS-Service/common.test.js b/test/QIDO-RS-Service/common.test.js
index 3f7e9888..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", () => {
@@ -42,7 +40,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 +80,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 +107,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 +133,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 +159,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 +189,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..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 = {
@@ -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..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 () => {
@@ -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: {