diff --git a/backend/api/package-lock.json b/backend/api/package-lock.json index 0fec54c..2fb8470 100644 --- a/backend/api/package-lock.json +++ b/backend/api/package-lock.json @@ -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", diff --git a/backend/api/package.json b/backend/api/package.json index 586cc07..cf2aa78 100644 --- a/backend/api/package.json +++ b/backend/api/package.json @@ -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", diff --git a/backend/api/prisma/schema.prisma b/backend/api/prisma/schema.prisma index 8ce2ec9..2cf7c05 100644 --- a/backend/api/prisma/schema.prisma +++ b/backend/api/prisma/schema.prisma @@ -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[] diff --git a/backend/api/src/app.module.ts b/backend/api/src/app.module.ts index c2c90a9..0cea9ea 100644 --- a/backend/api/src/app.module.ts +++ b/backend/api/src/app.module.ts @@ -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], diff --git a/backend/api/src/main.ts b/backend/api/src/main.ts index 11c4305..2695064 100644 --- a/backend/api/src/main.ts +++ b/backend/api/src/main.ts @@ -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('COOKIE_SECRET'))); await app.listen(configService.get('PORT') ?? 1323); } bootstrap(); - -// Module -// APP -// - Users -// -- Auth -// -- Profiles -// -- Register -// - Logs -// - RekamMedis -// - Obat -// - TindakanDokter diff --git a/backend/api/src/modules/auth/auth.controller.spec.ts b/backend/api/src/modules/auth/auth.controller.spec.ts new file mode 100644 index 0000000..27a31e6 --- /dev/null +++ b/backend/api/src/modules/auth/auth.controller.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/backend/api/src/modules/auth/auth.controller.ts b/backend/api/src/modules/auth/auth.controller.ts new file mode 100644 index 0000000..de591cd --- /dev/null +++ b/backend/api/src/modules/auth/auth.controller.ts @@ -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 { + return this.authService.registerUser(data); + } + + @Post('/login') + @Header('Content-Type', 'application/json') + @HttpCode(200) + loginUser(@Body() data: AuthDto): Promise { + return this.authService.signIn(data.username, data.password); + } +} diff --git a/backend/api/src/modules/auth/auth.guard.spec.ts b/backend/api/src/modules/auth/auth.guard.spec.ts new file mode 100644 index 0000000..b1496ee --- /dev/null +++ b/backend/api/src/modules/auth/auth.guard.spec.ts @@ -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(); + }); +}); diff --git a/backend/api/src/modules/auth/auth.guard.ts b/backend/api/src/modules/auth/auth.guard.ts new file mode 100644 index 0000000..537386e --- /dev/null +++ b/backend/api/src/modules/auth/auth.guard.ts @@ -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 { + 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('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; + } +} diff --git a/backend/api/src/modules/auth/auth.module.ts b/backend/api/src/modules/auth/auth.module.ts new file mode 100644 index 0000000..a914552 --- /dev/null +++ b/backend/api/src/modules/auth/auth.module.ts @@ -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('JWT_SECRET'), + signOptions: { expiresIn: '15m' }, + }), + }), + ], + providers: [AuthService], + controllers: [AuthController], +}) +export class AuthModule {} diff --git a/backend/api/src/user/auth/auth.service.spec.ts b/backend/api/src/modules/auth/auth.service.spec.ts similarity index 100% rename from backend/api/src/user/auth/auth.service.spec.ts rename to backend/api/src/modules/auth/auth.service.spec.ts diff --git a/backend/api/src/modules/auth/auth.service.ts b/backend/api/src/modules/auth/auth.service.ts new file mode 100644 index 0000000..c5be1e0 --- /dev/null +++ b/backend/api/src/modules/auth/auth.service.ts @@ -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 { + const salt = this.configService.get('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 { + 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, + }; + } +} diff --git a/backend/api/src/modules/auth/dto/auth.dto.ts b/backend/api/src/modules/auth/dto/auth.dto.ts new file mode 100644 index 0000000..28b2173 --- /dev/null +++ b/backend/api/src/modules/auth/dto/auth.dto.ts @@ -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; +} diff --git a/backend/api/src/modules/auth/dto/create-user.dto.ts b/backend/api/src/modules/auth/dto/create-user.dto.ts new file mode 100644 index 0000000..2ea6edc --- /dev/null +++ b/backend/api/src/modules/auth/dto/create-user.dto.ts @@ -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; +} diff --git a/backend/api/src/modules/auth/roles.decorator.ts b/backend/api/src/modules/auth/roles.decorator.ts new file mode 100644 index 0000000..1a811e2 --- /dev/null +++ b/backend/api/src/modules/auth/roles.decorator.ts @@ -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); diff --git a/backend/api/src/modules/auth/roles.guard.spec.ts b/backend/api/src/modules/auth/roles.guard.spec.ts new file mode 100644 index 0000000..96c1a77 --- /dev/null +++ b/backend/api/src/modules/auth/roles.guard.spec.ts @@ -0,0 +1,7 @@ +import { RolesGuard } from './roles.guard'; + +describe('RolesGuard', () => { + it('should be defined', () => { + expect(new RolesGuard()).toBeDefined(); + }); +}); diff --git a/backend/api/src/modules/auth/roles.guard.ts b/backend/api/src/modules/auth/roles.guard.ts new file mode 100644 index 0000000..7c7c642 --- /dev/null +++ b/backend/api/src/modules/auth/roles.guard.ts @@ -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( + 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'); + } +} diff --git a/backend/api/src/modules/log/dto/create-log.dto.ts b/backend/api/src/modules/log/dto/create-log.dto.ts new file mode 100644 index 0000000..93adb25 --- /dev/null +++ b/backend/api/src/modules/log/dto/create-log.dto.ts @@ -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; + }; +} diff --git a/backend/api/src/log/log.controller.spec.ts b/backend/api/src/modules/log/log.controller.spec.ts similarity index 100% rename from backend/api/src/log/log.controller.spec.ts rename to backend/api/src/modules/log/log.controller.spec.ts diff --git a/backend/api/src/log/log.controller.ts b/backend/api/src/modules/log/log.controller.ts similarity index 100% rename from backend/api/src/log/log.controller.ts rename to backend/api/src/modules/log/log.controller.ts diff --git a/backend/api/src/log/log.module.ts b/backend/api/src/modules/log/log.module.ts similarity index 56% rename from backend/api/src/log/log.module.ts rename to backend/api/src/modules/log/log.module.ts index 26fa50f..d6c6b36 100644 --- a/backend/api/src/log/log.module.ts +++ b/backend/api/src/modules/log/log.module.ts @@ -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 {} diff --git a/backend/api/src/user/register/register.service.spec.ts b/backend/api/src/modules/log/log.service.spec.ts similarity index 54% rename from backend/api/src/user/register/register.service.spec.ts rename to backend/api/src/modules/log/log.service.spec.ts index 949a13b..a2863e8 100644 --- a/backend/api/src/user/register/register.service.spec.ts +++ b/backend/api/src/modules/log/log.service.spec.ts @@ -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); + service = module.get(LogService); }); it('should be defined', () => { diff --git a/backend/api/src/user/auth/auth.service.ts b/backend/api/src/modules/log/log.service.ts similarity index 68% rename from backend/api/src/user/auth/auth.service.ts rename to backend/api/src/modules/log/log.service.ts index a41c649..2b8cb75 100644 --- a/backend/api/src/user/auth/auth.service.ts +++ b/backend/api/src/modules/log/log.service.ts @@ -1,4 +1,4 @@ import { Injectable } from '@nestjs/common'; @Injectable() -export class AuthService {} +export class LogService {} diff --git a/backend/api/src/obat/obat.controller.spec.ts b/backend/api/src/modules/obat/obat.controller.spec.ts similarity index 100% rename from backend/api/src/obat/obat.controller.spec.ts rename to backend/api/src/modules/obat/obat.controller.spec.ts diff --git a/backend/api/src/modules/obat/obat.controller.ts b/backend/api/src/modules/obat/obat.controller.ts new file mode 100644 index 0000000..cdc170c --- /dev/null +++ b/backend/api/src/modules/obat/obat.controller.ts @@ -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, + }); + } +} diff --git a/backend/api/src/modules/obat/obat.module.ts b/backend/api/src/modules/obat/obat.module.ts new file mode 100644 index 0000000..02f916d --- /dev/null +++ b/backend/api/src/modules/obat/obat.module.ts @@ -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 {} diff --git a/backend/api/src/modules/obat/obat.service.spec.ts b/backend/api/src/modules/obat/obat.service.spec.ts new file mode 100644 index 0000000..a50dbcd --- /dev/null +++ b/backend/api/src/modules/obat/obat.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/backend/api/src/modules/obat/obat.service.ts b/backend/api/src/modules/obat/obat.service.ts new file mode 100644 index 0000000..f04b4b2 --- /dev/null +++ b/backend/api/src/modules/obat/obat.service.ts @@ -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; + } +} diff --git a/backend/api/src/prisma/prisma.module.ts b/backend/api/src/modules/prisma/prisma.module.ts similarity index 100% rename from backend/api/src/prisma/prisma.module.ts rename to backend/api/src/modules/prisma/prisma.module.ts diff --git a/backend/api/src/prisma/prisma.service.spec.ts b/backend/api/src/modules/prisma/prisma.service.spec.ts similarity index 100% rename from backend/api/src/prisma/prisma.service.spec.ts rename to backend/api/src/modules/prisma/prisma.service.spec.ts diff --git a/backend/api/src/modules/prisma/prisma.service.ts b/backend/api/src/modules/prisma/prisma.service.ts new file mode 100644 index 0000000..55f5d77 --- /dev/null +++ b/backend/api/src/modules/prisma/prisma.service.ts @@ -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.'); + } +} diff --git a/backend/api/src/modules/rekammedis/dto/create-rekammedis.dto.ts b/backend/api/src/modules/rekammedis/dto/create-rekammedis.dto.ts new file mode 100644 index 0000000..a8fe3db --- /dev/null +++ b/backend/api/src/modules/rekammedis/dto/create-rekammedis.dto.ts @@ -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; +} diff --git a/backend/api/src/modules/rekammedis/dto/query-rekammedis.dto.ts b/backend/api/src/modules/rekammedis/dto/query-rekammedis.dto.ts new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/src/modules/rekammedis/dto/update-rekammedis.dto.ts b/backend/api/src/modules/rekammedis/dto/update-rekammedis.dto.ts new file mode 100644 index 0000000..d6d983f --- /dev/null +++ b/backend/api/src/modules/rekammedis/dto/update-rekammedis.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateRekamMedisDto } from './create-rekammedis.dto'; + +export class UpdateRekamMedisDto extends PartialType(CreateRekamMedisDto) {} diff --git a/backend/api/src/rekammedis/rekammedis.controller.spec.ts b/backend/api/src/modules/rekammedis/rekammedis.controller.spec.ts similarity index 100% rename from backend/api/src/rekammedis/rekammedis.controller.spec.ts rename to backend/api/src/modules/rekammedis/rekammedis.controller.spec.ts diff --git a/backend/api/src/modules/rekammedis/rekammedis.controller.ts b/backend/api/src/modules/rekammedis/rekammedis.controller.ts new file mode 100644 index 0000000..523588a --- /dev/null +++ b/backend/api/src/modules/rekammedis/rekammedis.controller.ts @@ -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); + } +} diff --git a/backend/api/src/modules/rekammedis/rekammedis.module.ts b/backend/api/src/modules/rekammedis/rekammedis.module.ts new file mode 100644 index 0000000..275e78f --- /dev/null +++ b/backend/api/src/modules/rekammedis/rekammedis.module.ts @@ -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 {} diff --git a/backend/api/src/modules/rekammedis/rekammedis.service.spec.ts b/backend/api/src/modules/rekammedis/rekammedis.service.spec.ts new file mode 100644 index 0000000..4af4fae --- /dev/null +++ b/backend/api/src/modules/rekammedis/rekammedis.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/backend/api/src/modules/rekammedis/rekammedis.service.ts b/backend/api/src/modules/rekammedis/rekammedis.service.ts new file mode 100644 index 0000000..1aeb8a8 --- /dev/null +++ b/backend/api/src/modules/rekammedis/rekammedis.service.ts @@ -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 { + 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; + } + } +} diff --git a/backend/api/src/tindakandokter/tindakandokter.controller.spec.ts b/backend/api/src/modules/tindakandokter/tindakandokter.controller.spec.ts similarity index 51% rename from backend/api/src/tindakandokter/tindakandokter.controller.spec.ts rename to backend/api/src/modules/tindakandokter/tindakandokter.controller.spec.ts index bd57567..c320c56 100644 --- a/backend/api/src/tindakandokter/tindakandokter.controller.spec.ts +++ b/backend/api/src/modules/tindakandokter/tindakandokter.controller.spec.ts @@ -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); + controller = module.get(TindakanDokterController); }); it('should be defined', () => { diff --git a/backend/api/src/modules/tindakandokter/tindakandokter.controller.ts b/backend/api/src/modules/tindakandokter/tindakandokter.controller.ts new file mode 100644 index 0000000..921efe4 --- /dev/null +++ b/backend/api/src/modules/tindakandokter/tindakandokter.controller.ts @@ -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, + }); + } +} diff --git a/backend/api/src/modules/tindakandokter/tindakandokter.module.ts b/backend/api/src/modules/tindakandokter/tindakandokter.module.ts new file mode 100644 index 0000000..8dcc68b --- /dev/null +++ b/backend/api/src/modules/tindakandokter/tindakandokter.module.ts @@ -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 {} diff --git a/backend/api/src/modules/tindakandokter/tindakandokter.service.spec.ts b/backend/api/src/modules/tindakandokter/tindakandokter.service.spec.ts new file mode 100644 index 0000000..ae75e21 --- /dev/null +++ b/backend/api/src/modules/tindakandokter/tindakandokter.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/backend/api/src/modules/tindakandokter/tindakandokter.service.ts b/backend/api/src/modules/tindakandokter/tindakandokter.service.ts new file mode 100644 index 0000000..c2f1c1c --- /dev/null +++ b/backend/api/src/modules/tindakandokter/tindakandokter.service.ts @@ -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; + } +} diff --git a/backend/api/src/user/profile/profile.service.spec.ts b/backend/api/src/modules/user/profile/profile.service.spec.ts similarity index 100% rename from backend/api/src/user/profile/profile.service.spec.ts rename to backend/api/src/modules/user/profile/profile.service.spec.ts diff --git a/backend/api/src/user/profile/profile.service.ts b/backend/api/src/modules/user/profile/profile.service.ts similarity index 100% rename from backend/api/src/user/profile/profile.service.ts rename to backend/api/src/modules/user/profile/profile.service.ts diff --git a/backend/api/src/user/user.controller.spec.ts b/backend/api/src/modules/user/user.controller.spec.ts similarity index 100% rename from backend/api/src/user/user.controller.spec.ts rename to backend/api/src/modules/user/user.controller.spec.ts diff --git a/backend/api/src/user/user.controller.ts b/backend/api/src/modules/user/user.controller.ts similarity index 67% rename from backend/api/src/user/user.controller.ts rename to backend/api/src/modules/user/user.controller.ts index a9e6cc9..3c6b42e 100644 --- a/backend/api/src/user/user.controller.ts +++ b/backend/api/src/modules/user/user.controller.ts @@ -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 { - return { message: 'List of all users' }; - // return 'List of all users'; + getAllUsers(): Promise { + return this.userService.getAllUsers(); } @Get('/set-cookie') diff --git a/backend/api/src/user/user.module.ts b/backend/api/src/modules/user/user.module.ts similarity index 59% rename from backend/api/src/user/user.module.ts rename to backend/api/src/modules/user/user.module.ts index 8336dff..48de7ca 100644 --- a/backend/api/src/user/user.module.ts +++ b/backend/api/src/modules/user/user.module.ts @@ -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 {} diff --git a/backend/api/src/user/user.service.spec.ts b/backend/api/src/modules/user/user.service.spec.ts similarity index 100% rename from backend/api/src/user/user.service.spec.ts rename to backend/api/src/modules/user/user.service.spec.ts diff --git a/backend/api/src/modules/user/user.service.ts b/backend/api/src/modules/user/user.service.ts new file mode 100644 index 0000000..de4040c --- /dev/null +++ b/backend/api/src/modules/user/user.service.ts @@ -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 { + return this.prisma.users.findMany(); + } +} diff --git a/backend/api/src/obat/obat.controller.ts b/backend/api/src/obat/obat.controller.ts deleted file mode 100644 index 4a6f55c..0000000 --- a/backend/api/src/obat/obat.controller.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Controller } from '@nestjs/common'; - -@Controller('obat') -export class ObatController {} diff --git a/backend/api/src/obat/obat.module.ts b/backend/api/src/obat/obat.module.ts deleted file mode 100644 index a639eeb..0000000 --- a/backend/api/src/obat/obat.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ObatController } from './obat.controller'; - -@Module({ - controllers: [ObatController] -}) -export class ObatModule {} diff --git a/backend/api/src/prisma/prisma.service.ts b/backend/api/src/prisma/prisma.service.ts deleted file mode 100644 index 0d01d91..0000000 --- a/backend/api/src/prisma/prisma.service.ts +++ /dev/null @@ -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'); - } -} diff --git a/backend/api/src/rekammedis/rekammedis.controller.ts b/backend/api/src/rekammedis/rekammedis.controller.ts deleted file mode 100644 index 6f2c3e2..0000000 --- a/backend/api/src/rekammedis/rekammedis.controller.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Controller } from '@nestjs/common'; - -@Controller('rekammedis') -export class RekamMedisController {} diff --git a/backend/api/src/rekammedis/rekammedis.module.ts b/backend/api/src/rekammedis/rekammedis.module.ts deleted file mode 100644 index 81c62c5..0000000 --- a/backend/api/src/rekammedis/rekammedis.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RekamMedisController } from './rekammedis.controller'; - -@Module({ - controllers: [RekamMedisController], -}) -export class RekamMedisModule {} diff --git a/backend/api/src/tindakandokter/tindakandokter.controller.ts b/backend/api/src/tindakandokter/tindakandokter.controller.ts deleted file mode 100644 index 24ce466..0000000 --- a/backend/api/src/tindakandokter/tindakandokter.controller.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Controller } from '@nestjs/common'; - -@Controller('tindakandokter') -export class TindakandokterController {} diff --git a/backend/api/src/tindakandokter/tindakandokter.module.ts b/backend/api/src/tindakandokter/tindakandokter.module.ts deleted file mode 100644 index 3e6837b..0000000 --- a/backend/api/src/tindakandokter/tindakandokter.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TindakandokterController } from './tindakandokter.controller'; - -@Module({ - controllers: [TindakandokterController] -}) -export class TindakandokterModule {} diff --git a/backend/api/src/user/register/register.service.ts b/backend/api/src/user/register/register.service.ts deleted file mode 100644 index 1d37731..0000000 --- a/backend/api/src/user/register/register.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class RegisterService {} diff --git a/backend/api/src/user/user.service.ts b/backend/api/src/user/user.service.ts deleted file mode 100644 index c2216e0..0000000 --- a/backend/api/src/user/user.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class UserService { - testService(): string { - return 'User service is working'; - } -}