feat: Authentication, Register users, Create Rekammedis + logs, Get All Users, Obat, Tindakan, Rekam Medis
This commit is contained in:
parent
fb09ff57d5
commit
d73a44cceb
287
backend/api/package-lock.json
generated
287
backend/api/package-lock.json
generated
|
|
@ -12,8 +12,13 @@
|
|||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.1",
|
||||
"@nestjs/mapped-types": "^2.1.0",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
|
|
@ -24,6 +29,7 @@
|
|||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/cookie-parser": "^1.4.9",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
|
|
@ -2303,14 +2309,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/common": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz",
|
||||
"integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.7.tgz",
|
||||
"integrity": "sha512-lwlObwGgIlpXSXYOTpfzdCepUyWomz6bv9qzGzzvpgspUxkj0Uz0fUJcvD44V8Ps7QhKW3lZBoYbXrH25UZrbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-type": "21.0.0",
|
||||
"iterare": "1.2.1",
|
||||
"load-esm": "1.0.2",
|
||||
"load-esm": "1.0.3",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
|
|
@ -2349,16 +2355,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/core": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.6.tgz",
|
||||
"integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.7.tgz",
|
||||
"integrity": "sha512-TyXFOwjhHv/goSgJ8i20K78jwTM0iSpk9GBcC2h3mf4MxNy+znI8m7nWjfoACjTkb89cTwDQetfTHtSfGLLaiA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/opencollective": "0.4.1",
|
||||
"fast-safe-stringify": "2.1.1",
|
||||
"iterare": "1.2.1",
|
||||
"path-to-regexp": "8.2.0",
|
||||
"path-to-regexp": "8.3.0",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
|
|
@ -2389,16 +2395,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/jwt": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.1.tgz",
|
||||
"integrity": "sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jsonwebtoken": "9.0.10",
|
||||
"jsonwebtoken": "9.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/mapped-types": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz",
|
||||
"integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"class-transformer": "^0.4.0 || ^0.5.0",
|
||||
"class-validator": "^0.13.0 || ^0.14.0",
|
||||
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"class-transformer": {
|
||||
"optional": true
|
||||
},
|
||||
"class-validator": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz",
|
||||
"integrity": "sha512-HErwPmKnk+loTq8qzu1up+k7FC6Kqa8x6lJ4cDw77KnTxLzsCaPt+jBvOq6UfICmfqcqCCf3dKXg+aObQp+kIQ==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.7.tgz",
|
||||
"integrity": "sha512-5T+GLdvTiGPKB4/P4PM9ftKUKNHJy8ThEFhZA3vQnXVL7Vf0rDr07TfVTySVu+XTh85m1lpFVuyFM6u6wLNsRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cors": "2.8.5",
|
||||
"express": "5.1.0",
|
||||
"multer": "2.0.2",
|
||||
"path-to-regexp": "8.2.0",
|
||||
"path-to-regexp": "8.3.0",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"funding": {
|
||||
|
|
@ -2509,9 +2548,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/testing": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.6.tgz",
|
||||
"integrity": "sha512-srYzzDNxGvVCe1j0SpTS9/ix75PKt6Sn6iMaH1rpJ6nj2g8vwNrhK0CoJJXvpCYgrnI+2WES2pprYnq8rAMYHA==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.7.tgz",
|
||||
"integrity": "sha512-QbtrgSlc3QVo6RHNxTTlyhaiobLLy8kvhOlgWHsoXRknybuRs7vZg4k5mo3ye6pITGeT3CrWIRpZjUsh5Wps5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -2864,6 +2903,16 @@
|
|||
"@babel/types": "^7.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bcrypt": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||
"integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
|
|
@ -3008,6 +3057,16 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jsonwebtoken": {
|
||||
"version": "9.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
|
||||
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/ms": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/methods": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||
|
|
@ -3022,11 +3081,16 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.18.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz",
|
||||
"integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
|
|
@ -3110,6 +3174,12 @@
|
|||
"@types/superagent": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/validator": {
|
||||
"version": "13.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz",
|
||||
"integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||
|
|
@ -4237,6 +4307,20 @@
|
|||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
|
|
@ -4387,6 +4471,12 @@
|
|||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
|
|
@ -4618,6 +4708,23 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
|
||||
"integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/validator": "^13.11.8",
|
||||
"libphonenumber-js": "^1.11.1",
|
||||
"validator": "^13.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||
|
|
@ -5185,6 +5292,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
|
@ -7641,6 +7757,49 @@
|
|||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
|
||||
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
@ -7675,6 +7834,12 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/libphonenumber-js": {
|
||||
"version": "1.12.24",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.24.tgz",
|
||||
"integrity": "sha512-l5IlyL9AONj4voSd7q9xkuQOL4u8Ty44puTic7J88CmdXkxfGsRfoVLXHCxppwehgpb/Chdb80FFehHqjN3ItQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
|
|
@ -7683,9 +7848,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/load-esm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz",
|
||||
"integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz",
|
||||
"integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -7737,6 +7902,42 @@
|
|||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
|
|
@ -7751,6 +7952,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||
|
|
@ -8129,6 +8336,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
|
||||
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-emoji": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
|
||||
|
|
@ -8146,6 +8362,17 @@
|
|||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
|
|
@ -8642,12 +8869,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
||||
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
|
|
@ -9233,7 +9461,6 @@
|
|||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
|
@ -10386,7 +10613,6 @@
|
|||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
|
|
@ -10512,6 +10738,15 @@
|
|||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.15",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
||||
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
|
|||
|
|
@ -23,8 +23,13 @@
|
|||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.1",
|
||||
"@nestjs/mapped-types": "^2.1.0",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
|
|
@ -35,6 +40,7 @@
|
|||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/cookie-parser": "^1.4.9",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
|
|
|
|||
|
|
@ -9,19 +9,20 @@ datasource db {
|
|||
}
|
||||
|
||||
model blockchain_log_queue {
|
||||
id BigInt @id @default(autoincrement())
|
||||
status String @default("PENDING") @db.VarChar(20)
|
||||
event String @db.VarChar(50)
|
||||
user_id BigInt?
|
||||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
payload Json?
|
||||
processed_at DateTime? @db.Timestamptz(6)
|
||||
users users? @relation(fields: [user_id], references: [id], onUpdate: NoAction, map: "fk_log_user")
|
||||
id BigInt @id @default(autoincrement())
|
||||
status String @default("PENDING") @db.VarChar(20)
|
||||
event String @db.VarChar(50)
|
||||
user_id BigInt
|
||||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
payload Json
|
||||
processed_at DateTime? @db.Timestamptz(6)
|
||||
transactionid String? @db.VarChar(64)
|
||||
users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "fk_log_user")
|
||||
}
|
||||
|
||||
model pemberian_obat {
|
||||
id Int @id @default(autoincrement())
|
||||
id_visit Int
|
||||
id_visit String @db.VarChar(25)
|
||||
obat String @db.VarChar(100)
|
||||
jumlah_obat Int
|
||||
aturan_pakai String?
|
||||
|
|
@ -30,7 +31,7 @@ model pemberian_obat {
|
|||
|
||||
model pemberian_tindakan {
|
||||
id Int @id @default(autoincrement())
|
||||
id_visit Int
|
||||
id_visit String @db.VarChar(25)
|
||||
tindakan String @db.VarChar(100)
|
||||
kategori_tindakan String? @db.VarChar(50)
|
||||
kelompok_tindakan String? @db.VarChar(50)
|
||||
|
|
@ -38,7 +39,7 @@ model pemberian_tindakan {
|
|||
}
|
||||
|
||||
model rekam_medis {
|
||||
id_visit Int @id @default(autoincrement())
|
||||
id_visit String @id @db.VarChar(25)
|
||||
waktu_visit DateTime @db.Timestamp(6)
|
||||
no_rm String @db.VarChar(20)
|
||||
nama_pasien String @db.VarChar(100)
|
||||
|
|
@ -55,8 +56,8 @@ model rekam_medis {
|
|||
nadi Int?
|
||||
suhu Decimal? @db.Decimal(4, 1)
|
||||
nafas Int?
|
||||
tinggi_badan Decimal? @db.Decimal(5, 2)
|
||||
berat_badan Decimal? @db.Decimal(5, 2)
|
||||
tinggi_badan Decimal? @db.Decimal(10, 5)
|
||||
berat_badan Decimal? @db.Decimal(10, 5)
|
||||
jenis_kasus String? @db.VarChar(50)
|
||||
tindak_lanjut String?
|
||||
pemberian_obat pemberian_obat[]
|
||||
|
|
|
|||
|
|
@ -1,25 +1,28 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { UserModule } from './user/user.module';
|
||||
import { LogModule } from './log/log.module';
|
||||
import { RekamMedisModule } from './rekammedis/rekammedis.module';
|
||||
import { ObatModule } from './obat/obat.module';
|
||||
import { TindakandokterModule } from './tindakandokter/tindakandokter.module';
|
||||
import { UserModule } from './modules/user/user.module';
|
||||
import { LogModule } from './modules/log/log.module';
|
||||
import { RekamMedisModule } from './modules/rekammedis/rekammedis.module';
|
||||
import { ObatModule } from './modules/obat/obat.module';
|
||||
import { TindakanDokterModule } from './modules/tindakandokter/tindakandokter.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { PrismaModule } from './modules/prisma/prisma.module';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
AuthModule,
|
||||
UserModule,
|
||||
TindakanDokterModule,
|
||||
LogModule,
|
||||
RekamMedisModule,
|
||||
ObatModule,
|
||||
TindakandokterModule,
|
||||
PrismaModule,
|
||||
AuthModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
|
|
|||
|
|
@ -2,22 +2,25 @@ import { NestFactory } from '@nestjs/core';
|
|||
import { AppModule } from './app.module';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
||||
(BigInt.prototype as any).toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const configService = app.get(ConfigService);
|
||||
app.setGlobalPrefix('api/');
|
||||
app.enableCors();
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
}),
|
||||
);
|
||||
app.use(cookieParser(configService.get<string>('COOKIE_SECRET')));
|
||||
await app.listen(configService.get<number>('PORT') ?? 1323);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
// Module
|
||||
// APP
|
||||
// - Users
|
||||
// -- Auth
|
||||
// -- Profiles
|
||||
// -- Register
|
||||
// - Logs
|
||||
// - RekamMedis
|
||||
// - Obat
|
||||
// - TindakanDokter
|
||||
|
|
|
|||
18
backend/api/src/modules/auth/auth.controller.spec.ts
Normal file
18
backend/api/src/modules/auth/auth.controller.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthController } from './auth.controller';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AuthController>(AuthController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
35
backend/api/src/modules/auth/auth.controller.ts
Normal file
35
backend/api/src/modules/auth/auth.controller.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Header,
|
||||
HttpCode,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { CreateUserDto, CreateUserDtoResponse } from './dto/create-user.dto';
|
||||
import { AuthDto, AuthDtoResponse, UserRole } from './dto/auth.dto';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { RolesGuard } from './roles.guard';
|
||||
import { Roles } from './roles.decorator';
|
||||
|
||||
@Controller('/auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@Post('/register')
|
||||
@Header('Content-Type', 'application/json')
|
||||
@HttpCode(201)
|
||||
@UseGuards(AuthGuard, RolesGuard)
|
||||
@Roles(UserRole.Admin)
|
||||
registerUser(@Body() data: CreateUserDto): Promise<CreateUserDtoResponse> {
|
||||
return this.authService.registerUser(data);
|
||||
}
|
||||
|
||||
@Post('/login')
|
||||
@Header('Content-Type', 'application/json')
|
||||
@HttpCode(200)
|
||||
loginUser(@Body() data: AuthDto): Promise<AuthDtoResponse> {
|
||||
return this.authService.signIn(data.username, data.password);
|
||||
}
|
||||
}
|
||||
9
backend/api/src/modules/auth/auth.guard.spec.ts
Normal file
9
backend/api/src/modules/auth/auth.guard.spec.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { JwtService } from '@nestjs/jwt';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
describe('AuthGuard', () => {
|
||||
it('should be defined', () => {
|
||||
expect(new AuthGuard(new JwtService(), new ConfigService())).toBeDefined();
|
||||
});
|
||||
});
|
||||
38
backend/api/src/modules/auth/auth.guard.ts
Normal file
38
backend/api/src/modules/auth/auth.guard.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: this.configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
request['user'] = payload;
|
||||
} catch {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: any): string | undefined {
|
||||
const [type, token] = request.headers?.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
}
|
||||
25
backend/api/src/modules/auth/auth.module.ts
Normal file
25
backend/api/src/modules/auth/auth.module.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
@Module({
|
||||
exports: [AuthService],
|
||||
imports: [
|
||||
PrismaModule,
|
||||
ConfigModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
secret: configService.get<string>('JWT_SECRET'),
|
||||
signOptions: { expiresIn: '15m' },
|
||||
}),
|
||||
}),
|
||||
],
|
||||
providers: [AuthService],
|
||||
controllers: [AuthController],
|
||||
})
|
||||
export class AuthModule {}
|
||||
74
backend/api/src/modules/auth/auth.service.ts
Normal file
74
backend/api/src/modules/auth/auth.service.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { PrismaService } from '@api/modules/prisma/prisma.service';
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthDtoResponse, UserRole } from './dto/auth.dto';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { CreateUserDto, CreateUserDtoResponse } from './dto/create-user.dto';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Prisma } from '@dist/generated/prisma';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async registerUser(data: CreateUserDto): Promise<CreateUserDtoResponse> {
|
||||
const salt = this.configService.get<number>('BCRYPT_SALT') ?? 10;
|
||||
const hashedPassword = await bcrypt.hash(data.password, salt);
|
||||
|
||||
try {
|
||||
const userCreated = await this.prisma.users.create({
|
||||
data: {
|
||||
nama_lengkap: data.nama_lengkap,
|
||||
username: data.username,
|
||||
password_hash: hashedPassword,
|
||||
role: data.role || 'user',
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: userCreated.id,
|
||||
nama_lengkap: userCreated.nama_lengkap,
|
||||
username: userCreated.username,
|
||||
role: userCreated.role as UserRole,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
if (error.code === 'P2002') {
|
||||
throw new ConflictException('Username ini sudah terdaftar');
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async signIn(username: string, password: string): Promise<AuthDtoResponse> {
|
||||
const user = await this.prisma.users.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
|
||||
if (!user || !(await bcrypt.compare(password, user.password_hash))) {
|
||||
throw new UnauthorizedException('Username atau password salah');
|
||||
}
|
||||
|
||||
const token = await this.jwtService.signAsync({
|
||||
sub: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
});
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role as UserRole,
|
||||
token,
|
||||
};
|
||||
}
|
||||
}
|
||||
35
backend/api/src/modules/auth/dto/auth.dto.ts
Normal file
35
backend/api/src/modules/auth/dto/auth.dto.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { IsString, IsNotEmpty, Length, IsEnum } from 'class-validator';
|
||||
import { Expose, Transform } from 'class-transformer';
|
||||
|
||||
export enum UserRole {
|
||||
Admin = 'admin',
|
||||
User = 'user',
|
||||
}
|
||||
|
||||
export class AuthDto {
|
||||
@IsNotEmpty({ message: 'Username wajib diisi' })
|
||||
@IsString({ message: 'Username harus berupa string' })
|
||||
@Length(1, 100, { message: 'Username maksimal 100 karakter' })
|
||||
username: string;
|
||||
|
||||
@IsNotEmpty({ message: 'Password wajib diisi' })
|
||||
@IsString({ message: 'Password harus berupa string' })
|
||||
@Length(6, undefined, { message: 'Password minimal 6 karakter' })
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class AuthDtoResponse {
|
||||
@Expose()
|
||||
@Transform(({ value }: { value: bigint }) => value.toString())
|
||||
id: bigint;
|
||||
|
||||
@Expose()
|
||||
username: string;
|
||||
|
||||
@Expose()
|
||||
@IsEnum(UserRole)
|
||||
role: UserRole;
|
||||
|
||||
@Expose()
|
||||
token: string;
|
||||
}
|
||||
48
backend/api/src/modules/auth/dto/create-user.dto.ts
Normal file
48
backend/api/src/modules/auth/dto/create-user.dto.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
Length,
|
||||
IsOptional,
|
||||
IsEnum,
|
||||
} from 'class-validator';
|
||||
import { Expose, Transform } from 'class-transformer';
|
||||
import { UserRole } from './auth.dto';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsNotEmpty({ message: 'Nama lengkap wajib diisi' })
|
||||
@IsString({ message: 'Nama lengkap harus berupa string' })
|
||||
@Length(1, 255, { message: 'Nama lengkap maksimal 255 karakter' })
|
||||
nama_lengkap: string;
|
||||
|
||||
@IsNotEmpty({ message: 'Username wajib diisi' })
|
||||
@IsString({ message: 'Username harus berupa string' })
|
||||
@Length(1, 100, { message: 'Username maksimal 100 karakter' })
|
||||
username: string;
|
||||
|
||||
@IsNotEmpty({ message: 'Password wajib diisi' })
|
||||
@IsString({ message: 'Password harus berupa string' })
|
||||
@Length(6, 100, {
|
||||
message: 'Password minimal 6 karakter dan maksimal 100 karakter',
|
||||
})
|
||||
password: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: 'Role harus berupa string' })
|
||||
@IsEnum(UserRole, { message: 'Role harus "admin" atau "user"' })
|
||||
role?: UserRole;
|
||||
}
|
||||
|
||||
export class CreateUserDtoResponse {
|
||||
@Expose()
|
||||
@Transform(({ value }: { value: bigint }) => value.toString())
|
||||
id: bigint;
|
||||
|
||||
@Expose()
|
||||
nama_lengkap: string;
|
||||
|
||||
@Expose()
|
||||
username: string;
|
||||
|
||||
@Expose()
|
||||
role?: UserRole;
|
||||
}
|
||||
6
backend/api/src/modules/auth/roles.decorator.ts
Normal file
6
backend/api/src/modules/auth/roles.decorator.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { SetMetadata } from '@nestjs/common';
|
||||
import { UserRole } from './dto/auth.dto';
|
||||
|
||||
export const ROLES_KEY = 'roles';
|
||||
|
||||
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);
|
||||
7
backend/api/src/modules/auth/roles.guard.spec.ts
Normal file
7
backend/api/src/modules/auth/roles.guard.spec.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { RolesGuard } from './roles.guard';
|
||||
|
||||
describe('RolesGuard', () => {
|
||||
it('should be defined', () => {
|
||||
expect(new RolesGuard()).toBeDefined();
|
||||
});
|
||||
});
|
||||
39
backend/api/src/modules/auth/roles.guard.ts
Normal file
39
backend/api/src/modules/auth/roles.guard.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ROLES_KEY } from './roles.decorator';
|
||||
import { UserRole } from './dto/auth.dto';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(
|
||||
ROLES_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
|
||||
if (!requiredRoles) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
|
||||
if (!user?.role) {
|
||||
throw new ForbiddenException('Insufficient permissions (no role)');
|
||||
}
|
||||
|
||||
const hasRole = requiredRoles.some((role) => user.role === role);
|
||||
|
||||
if (hasRole) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new ForbiddenException('You do not have the required role');
|
||||
}
|
||||
}
|
||||
40
backend/api/src/modules/log/dto/create-log.dto.ts
Normal file
40
backend/api/src/modules/log/dto/create-log.dto.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { IsString, IsNotEmpty, Length, IsJSON, IsEnum } from 'class-validator';
|
||||
|
||||
export class CreateLogDto {
|
||||
@IsNotEmpty({ message: 'Event wajib diisi' })
|
||||
@IsString({ message: 'Event harus berupa string' })
|
||||
@IsEnum(
|
||||
[
|
||||
'tindakan_dokter_created',
|
||||
'obat_given',
|
||||
'rekam_medis_created',
|
||||
'tindakan_dokter_updated',
|
||||
'obat_updated',
|
||||
'rekam_medis_updated',
|
||||
'tindakan_dokter_deleted',
|
||||
'obat_deleted',
|
||||
'rekam_medis_deleted',
|
||||
],
|
||||
{
|
||||
message: 'Event tidak valid',
|
||||
},
|
||||
)
|
||||
@Length(1, 100, { message: 'Event maksimal 100 karakter' })
|
||||
event: string;
|
||||
|
||||
@IsNotEmpty({ message: 'Payload wajib diisi' })
|
||||
@IsJSON({ message: 'Payload harus berupa JSON yang valid' })
|
||||
payload: {
|
||||
dokter_id: number;
|
||||
visit_id: string;
|
||||
tindakan?: string;
|
||||
kategori_tindakan?: string;
|
||||
kelompok_tindakan?: string;
|
||||
obat?: string;
|
||||
jumlah_obat?: number;
|
||||
aturan_pakai?: string;
|
||||
anamnese?: string;
|
||||
jenis_kasus?: string;
|
||||
tindak_lanjut?: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { LogController } from './log.controller';
|
||||
import { LogService } from './log.service';
|
||||
|
||||
@Module({
|
||||
controllers: [LogController]
|
||||
controllers: [LogController],
|
||||
providers: [LogService]
|
||||
})
|
||||
export class LogModule {}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { RegisterService } from './register.service';
|
||||
import { LogService } from './log.service';
|
||||
|
||||
describe('RegisterService', () => {
|
||||
let service: RegisterService;
|
||||
describe('LogService', () => {
|
||||
let service: LogService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [RegisterService],
|
||||
providers: [LogService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<RegisterService>(RegisterService);
|
||||
service = module.get<LogService>(LogService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {}
|
||||
export class LogService {}
|
||||
26
backend/api/src/modules/obat/obat.controller.ts
Normal file
26
backend/api/src/modules/obat/obat.controller.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { ObatService } from './obat.service';
|
||||
|
||||
@Controller('obat')
|
||||
export class ObatController {
|
||||
constructor(private readonly obatService: ObatService) {}
|
||||
|
||||
@Get('/')
|
||||
async getAllObat(
|
||||
@Query('take') take: number,
|
||||
@Query('skip') skip: number,
|
||||
@Query('page') page: number,
|
||||
@Query('orderBy') orderBy: string,
|
||||
@Query('obat') obat: string,
|
||||
@Query('order') order: 'asc' | 'desc',
|
||||
) {
|
||||
return await this.obatService.getAllObat({
|
||||
take,
|
||||
skip,
|
||||
page,
|
||||
orderBy: orderBy ? { [orderBy]: order || 'asc' } : undefined,
|
||||
obat,
|
||||
order,
|
||||
});
|
||||
}
|
||||
}
|
||||
11
backend/api/src/modules/obat/obat.module.ts
Normal file
11
backend/api/src/modules/obat/obat.module.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ObatController } from './obat.controller';
|
||||
import { ObatService } from './obat.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [ObatController],
|
||||
providers: [ObatService],
|
||||
})
|
||||
export class ObatModule {}
|
||||
18
backend/api/src/modules/obat/obat.service.spec.ts
Normal file
18
backend/api/src/modules/obat/obat.service.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ObatService } from './obat.service';
|
||||
|
||||
describe('ObatService', () => {
|
||||
let service: ObatService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ObatService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ObatService>(ObatService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
37
backend/api/src/modules/obat/obat.service.ts
Normal file
37
backend/api/src/modules/obat/obat.service.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ObatService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getAllObat(params: {
|
||||
take?: number;
|
||||
skip?: number;
|
||||
page?: number;
|
||||
orderBy?: any;
|
||||
obat?: string;
|
||||
order?: 'asc' | 'desc';
|
||||
}) {
|
||||
const { skip, page, orderBy, order, obat } = params;
|
||||
const take = params.take ? parseInt(params.take.toString()) : 10;
|
||||
const skipValue = skip
|
||||
? parseInt(skip.toString())
|
||||
: page
|
||||
? (parseInt(page.toString()) - 1) * take
|
||||
: 0;
|
||||
const results = await this.prisma.pemberian_obat.findMany({
|
||||
skip: skipValue,
|
||||
take: take,
|
||||
where: {
|
||||
obat: obat ? { contains: obat } : undefined,
|
||||
},
|
||||
orderBy: orderBy
|
||||
? { [Object.keys(orderBy)[0]]: order || 'asc' }
|
||||
: { id: 'asc' },
|
||||
});
|
||||
|
||||
console.log('Fetched Obat:', results.length);
|
||||
return results;
|
||||
}
|
||||
}
|
||||
10
backend/api/src/modules/prisma/prisma.service.ts
Normal file
10
backend/api/src/modules/prisma/prisma.service.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@dist/generated/prisma';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
console.log('PrismaService has connected to the database.');
|
||||
}
|
||||
}
|
||||
115
backend/api/src/modules/rekammedis/dto/create-rekammedis.dto.ts
Normal file
115
backend/api/src/modules/rekammedis/dto/create-rekammedis.dto.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsDateString,
|
||||
Min,
|
||||
Max,
|
||||
Length,
|
||||
Matches,
|
||||
IsIn,
|
||||
} from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export class CreateRekamMedisDto {
|
||||
@IsNotEmpty({ message: 'Nomor rekam medis (no_rm) wajib diisi' })
|
||||
@IsString()
|
||||
@Length(1, 20, { message: 'Nomor rekam medis maksimal 20 karakter' })
|
||||
no_rm: string;
|
||||
|
||||
@IsNotEmpty({ message: 'Nama pasien wajib diisi' })
|
||||
@IsString()
|
||||
@Length(1, 100, { message: 'Nama pasien maksimal 100 karakter' })
|
||||
nama_pasien: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt({ message: 'Umur harus berupa angka bulat' })
|
||||
@Min(0, { message: 'Umur tidak boleh negatif' })
|
||||
@Max(150, { message: 'Umur tidak valid' })
|
||||
@Transform(({ value }) => (value ? parseInt(value) : null))
|
||||
umur?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsIn(['L', 'P', 'l', 'p'], {
|
||||
message: 'Jenis kelamin harus "L" (Laki-laki) atau "P" (Perempuan)',
|
||||
})
|
||||
@Transform(({ value }) => value?.toUpperCase())
|
||||
jenis_kelamin?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsIn(['A', 'B', 'AB', 'O', '-'], {
|
||||
message: 'Golongan darah harus A, B, AB, O, atau -',
|
||||
})
|
||||
@Length(1, 2)
|
||||
gol_darah?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Length(1, 100)
|
||||
pekerjaan?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Length(1, 100)
|
||||
suku?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
kode_diagnosa?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
diagnosa?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
anamnese?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt({ message: 'Tekanan darah sistolik harus berupa angka bulat' })
|
||||
@Transform(({ value }) => (value ? parseInt(value) : null))
|
||||
sistolik?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt({ message: 'Tekanan darah diastolik harus berupa angka bulat' })
|
||||
@Transform(({ value }) => (value ? parseInt(value) : null))
|
||||
diastolik?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt({ message: 'Nadi harus berupa angka bulat' })
|
||||
@Transform(({ value }) => (value ? parseInt(value) : null))
|
||||
nadi?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber({}, { message: 'Suhu harus berupa angka' })
|
||||
@Transform(({ value }) => (value ? parseFloat(value) : null))
|
||||
suhu?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt({ message: 'Pernapasan harus berupa angka bulat' })
|
||||
@Transform(({ value }) => (value ? parseInt(value) : null))
|
||||
nafas?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber({}, { message: 'Tinggi badan harus berupa angka' })
|
||||
@Transform(({ value }) => (value ? parseFloat(value) : null))
|
||||
tinggi_badan?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber({}, { message: 'Berat badan harus berupa angka' })
|
||||
@Transform(({ value }) => (value ? parseFloat(value) : null))
|
||||
berat_badan?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Length(1, 50)
|
||||
jenis_kasus?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
tindak_lanjut?: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateRekamMedisDto } from './create-rekammedis.dto';
|
||||
|
||||
export class UpdateRekamMedisDto extends PartialType(CreateRekamMedisDto) {}
|
||||
46
backend/api/src/modules/rekammedis/rekammedis.controller.ts
Normal file
46
backend/api/src/modules/rekammedis/rekammedis.controller.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Header,
|
||||
HttpCode,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { RekammedisService } from './rekammedis.service';
|
||||
import { CreateRekamMedisDto } from './dto/create-rekammedis.dto';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
|
||||
@Controller('/rekammedis')
|
||||
export class RekamMedisController {
|
||||
constructor(private readonly rekammedisService: RekammedisService) {}
|
||||
|
||||
@Get('/')
|
||||
@Header('Content-Type', 'application/json')
|
||||
@HttpCode(200)
|
||||
@UseGuards(AuthGuard)
|
||||
async getAllRekamMedis(
|
||||
@Query('take') take: number,
|
||||
@Query('skip') skip: number,
|
||||
@Query('page') page: number,
|
||||
@Query('orderBy') orderBy: string,
|
||||
@Query('no_rm') no_rm: string,
|
||||
@Query('order') order: 'asc' | 'desc',
|
||||
) {
|
||||
return this.rekammedisService.getAllRekamMedis({
|
||||
take,
|
||||
skip,
|
||||
page,
|
||||
orderBy,
|
||||
no_rm,
|
||||
order,
|
||||
});
|
||||
}
|
||||
|
||||
@Post('/')
|
||||
@Header('Content-Type', 'application/json')
|
||||
async createRekamMedis(@Body() dto: CreateRekamMedisDto) {
|
||||
return this.rekammedisService.createRekamMedis(dto);
|
||||
}
|
||||
}
|
||||
12
backend/api/src/modules/rekammedis/rekammedis.module.ts
Normal file
12
backend/api/src/modules/rekammedis/rekammedis.module.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { RekamMedisController } from './rekammedis.controller';
|
||||
import { RekammedisService } from './rekammedis.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, JwtModule],
|
||||
controllers: [RekamMedisController],
|
||||
providers: [RekammedisService],
|
||||
})
|
||||
export class RekamMedisModule {}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { RekammedisService } from '../rekammedis/rekammedis.service';
|
||||
|
||||
describe('RekammedisService', () => {
|
||||
let service: RekammedisService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [RekammedisService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<RekammedisService>(RekammedisService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
106
backend/api/src/modules/rekammedis/rekammedis.service.ts
Normal file
106
backend/api/src/modules/rekammedis/rekammedis.service.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { Prisma, rekam_medis } from '@dist/generated/prisma';
|
||||
import { CreateRekamMedisDto } from './dto/create-rekammedis.dto';
|
||||
import { CreateLogDto } from '../log/dto/create-log.dto';
|
||||
|
||||
@Injectable()
|
||||
export class RekammedisService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getAllRekamMedis(params: {
|
||||
take?: number;
|
||||
skip?: number;
|
||||
page?: number;
|
||||
orderBy?: any;
|
||||
no_rm?: string;
|
||||
order?: 'asc' | 'desc';
|
||||
}): Promise<rekam_medis[]> {
|
||||
const { skip, page, orderBy, order, no_rm } = params;
|
||||
const take = params.take ? parseInt(params.take.toString()) : 10;
|
||||
const skipValue = skip
|
||||
? parseInt(skip.toString())
|
||||
: page
|
||||
? (parseInt(page.toString()) - 1) * take
|
||||
: 0;
|
||||
|
||||
const results = await this.prisma.rekam_medis.findMany({
|
||||
skip: skipValue,
|
||||
take: take,
|
||||
where: {
|
||||
no_rm: no_rm ? no_rm : undefined,
|
||||
},
|
||||
orderBy: orderBy
|
||||
? { [orderBy]: order || 'asc' }
|
||||
: { waktu_visit: order ? order : 'asc' },
|
||||
});
|
||||
|
||||
console.log('Fetched Rekam Medis:', results.length);
|
||||
return results;
|
||||
}
|
||||
|
||||
async createRekamMedis(data: CreateRekamMedisDto) {
|
||||
const latestId = await this.prisma.rekam_medis.findFirst({
|
||||
orderBy: { waktu_visit: 'desc' },
|
||||
});
|
||||
|
||||
let newId = '';
|
||||
let xCounter = 0;
|
||||
let rekamMedis: Prisma.rekam_medisCreateInput;
|
||||
|
||||
for (let i = (latestId?.id_visit?.length ?? 0) - 1; i >= 0; i--) {
|
||||
if (latestId?.id_visit[i] === 'X') {
|
||||
xCounter++;
|
||||
} else {
|
||||
newId = latestId?.id_visit?.substring(0, i + 1) || '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (xCounter < 1) {
|
||||
newId = (parseInt(latestId?.id_visit || '0', 10) + 1).toString();
|
||||
} else {
|
||||
newId = (parseInt(newId || '0', 10) + 1).toString();
|
||||
}
|
||||
|
||||
rekamMedis = {
|
||||
...data,
|
||||
id_visit: newId,
|
||||
waktu_visit: new Date(),
|
||||
};
|
||||
|
||||
const logData: CreateLogDto = {
|
||||
event: 'rekam_medis_created',
|
||||
payload: {
|
||||
dokter_id: 123,
|
||||
visit_id: newId,
|
||||
anamnese: data.anamnese,
|
||||
jenis_kasus: data.jenis_kasus,
|
||||
tindak_lanjut: data.tindak_lanjut,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const newRekamMedis = await this.prisma.$transaction(async (tx) => {
|
||||
const createdRekamMedis = await tx.rekam_medis.create({
|
||||
data: rekamMedis,
|
||||
});
|
||||
|
||||
await tx.blockchain_log_queue.create({
|
||||
data: {
|
||||
event: logData.event,
|
||||
user_id: 9,
|
||||
payload: logData.payload,
|
||||
},
|
||||
});
|
||||
|
||||
return createdRekamMedis;
|
||||
});
|
||||
|
||||
return newRekamMedis;
|
||||
} catch (error) {
|
||||
console.error('Error creating Rekam Medis:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TindakandokterController } from './tindakandokter.controller';
|
||||
import { TindakanDokterController } from './tindakandokter.controller';
|
||||
|
||||
describe('TindakandokterController', () => {
|
||||
let controller: TindakandokterController;
|
||||
describe('TindakanDokterController', () => {
|
||||
let controller: TindakanDokterController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [TindakandokterController],
|
||||
controllers: [TindakanDokterController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<TindakandokterController>(TindakandokterController);
|
||||
controller = module.get<TindakanDokterController>(TindakanDokterController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Header,
|
||||
HttpCode,
|
||||
Param,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { TindakanDokterService } from './tindakandokter.service';
|
||||
|
||||
@Controller('/tindakan')
|
||||
export class TindakanDokterController {
|
||||
constructor(private tindakanDokterService: TindakanDokterService) {}
|
||||
|
||||
@Get('/')
|
||||
async getAllTindakanDokter(
|
||||
@Query('take') take: number,
|
||||
@Query('tindakan') tindakan: string,
|
||||
@Query('skip') skip: number,
|
||||
@Query('page') page: number,
|
||||
@Query('orderBy') orderBy: string,
|
||||
@Query('order') order: 'asc' | 'desc',
|
||||
) {
|
||||
return await this.tindakanDokterService.getAllTindakanDokter({
|
||||
take,
|
||||
tindakan,
|
||||
skip,
|
||||
page,
|
||||
orderBy: orderBy ? { [orderBy]: order || 'asc' } : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TindakanDokterController } from './tindakandokter.controller';
|
||||
import { TindakanDokterService } from './tindakandokter.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [TindakanDokterController],
|
||||
providers: [TindakanDokterService],
|
||||
})
|
||||
export class TindakanDokterModule {}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TindakanDokterService } from './tindakandokter.service';
|
||||
|
||||
describe('TindakandokterService', () => {
|
||||
let service: TindakanDokterService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [TindakanDokterService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<TindakanDokterService>(TindakanDokterService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { Prisma } from '@dist/generated/prisma';
|
||||
|
||||
@Injectable()
|
||||
export class TindakanDokterService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getAllTindakanDokter(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
page?: number;
|
||||
tindakan?: string;
|
||||
orderBy?: Prisma.pemberian_tindakanOrderByWithRelationInput;
|
||||
order?: 'asc' | 'desc';
|
||||
}) {
|
||||
const { skip, page, tindakan, orderBy, order } = params;
|
||||
const take = params.take ? parseInt(params.take.toString()) : 10;
|
||||
const skipValue = skip
|
||||
? parseInt(skip.toString())
|
||||
: page
|
||||
? (parseInt(page.toString()) - 1) * take
|
||||
: 0;
|
||||
const results = await this.prisma.pemberian_tindakan.findMany({
|
||||
skip: skipValue,
|
||||
take: take,
|
||||
where: {
|
||||
tindakan: tindakan ? { contains: tindakan } : undefined,
|
||||
},
|
||||
orderBy: orderBy
|
||||
? { [Object.keys(orderBy)[0]]: order || 'asc' }
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
Body,
|
||||
ClassSerializerInterceptor,
|
||||
Controller,
|
||||
Get,
|
||||
Header,
|
||||
|
|
@ -8,23 +10,16 @@ import {
|
|||
Query,
|
||||
Req,
|
||||
Res,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import type { Request, Response } from 'express';
|
||||
import { UserService } from './user.service';
|
||||
import { users } from '@dist/generated/prisma';
|
||||
|
||||
@Controller('/api/users')
|
||||
@Controller('/users')
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
export class UserController {
|
||||
constructor(private service: UserService) {}
|
||||
|
||||
@Post('/register')
|
||||
registerUser(): string {
|
||||
return this.service.testService();
|
||||
}
|
||||
|
||||
@Post('/login')
|
||||
loginUser(): string {
|
||||
return 'User logged in successfully';
|
||||
}
|
||||
constructor(private userService: UserService) {}
|
||||
|
||||
@Get('/profile/:id')
|
||||
getUserProfile(@Param('id') id: string): string {
|
||||
|
|
@ -34,9 +29,8 @@ export class UserController {
|
|||
@Get('/')
|
||||
@Header('Content-Type', 'application/json')
|
||||
@HttpCode(200)
|
||||
getAllUsers(): Record<string, string> {
|
||||
return { message: 'List of all users' };
|
||||
// return 'List of all users';
|
||||
getAllUsers(): Promise<users[]> {
|
||||
return this.userService.getAllUsers();
|
||||
}
|
||||
|
||||
@Get('/set-cookie')
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { UserController } from './user.controller';
|
||||
import { UserService } from './user.service';
|
||||
import { AuthService } from './auth/auth.service';
|
||||
import { RegisterService } from './register/register.service';
|
||||
import { ProfileService } from './profile/profile.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [UserController],
|
||||
providers: [UserService, AuthService, RegisterService, ProfileService],
|
||||
providers: [UserService, ProfileService],
|
||||
})
|
||||
export class UserModule {}
|
||||
12
backend/api/src/modules/user/user.service.ts
Normal file
12
backend/api/src/modules/user/user.service.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { users } from '@dist/generated/prisma';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getAllUsers(): Promise<users[]> {
|
||||
return this.prisma.users.findMany();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
|
||||
@Controller('obat')
|
||||
export class ObatController {}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ObatController } from './obat.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ObatController]
|
||||
})
|
||||
export class ObatModule {}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaClient } from '@dist/generated/prisma';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient {
|
||||
constructor() {
|
||||
super();
|
||||
console.log('PrismaService initialized');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
|
||||
@Controller('rekammedis')
|
||||
export class RekamMedisController {}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { RekamMedisController } from './rekammedis.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [RekamMedisController],
|
||||
})
|
||||
export class RekamMedisModule {}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
|
||||
@Controller('tindakandokter')
|
||||
export class TindakandokterController {}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TindakandokterController } from './tindakandokter.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [TindakandokterController]
|
||||
})
|
||||
export class TindakandokterModule {}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class RegisterService {}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
testService(): string {
|
||||
return 'User service is working';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user