Merge branch 'dev'
This commit is contained in:
commit
2b843d7e13
44 changed files with 4242 additions and 105 deletions
11
.env.example
11
.env.example
|
|
@ -7,4 +7,15 @@ KC_SERVICE_ACCOUNT_SECRET=
|
|||
APP_HOST=0.0.0.0
|
||||
APP_PORT=3000
|
||||
|
||||
MINIO_HOST=192.168.1.20
|
||||
MINIO_PORT=9000
|
||||
MINIO_ACCESS_KEY=
|
||||
MINIO_SECRET_KEY=
|
||||
MINIO_BUCKET=jws-dev
|
||||
|
||||
ELASTICSEARCH_PROTOCOL=http
|
||||
ELASTICSEARCH_HOST=192.168.1.20
|
||||
ELASTICSEARCH_PORT=9200
|
||||
ELASTICSEARCH_INDEX=jws-log-index
|
||||
|
||||
DATABASE_URL=postgresql://postgres:1234@192.168.1.20:5432/dev_1?schema=public
|
||||
|
|
|
|||
74
.github/workflows/release.yml
vendored
Normal file
74
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
name: release-test
|
||||
run-name: release-test ${{ github.actor }}
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "version-[0-9]+.[0-9]+.[0-9]+"
|
||||
workflow_dispatch:
|
||||
env:
|
||||
REGISTRY: docker.frappet.com
|
||||
IMAGE_NAME: jws/jws-backend
|
||||
DEPLOY_HOST: 49.0.91.80
|
||||
COMPOSE_PATH: /home/frappet/docker/jws
|
||||
jobs:
|
||||
# act workflow_dispatch -W .github/workflows/release.yaml --input IMAGE_VER=test-v1 -s DOCKER_USER=sorawit -s DOCKER_PASS=P@ssword -s SSH_PASSWORD=P@ssw0rd
|
||||
release-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# skip Set up QEMU because it fail on act and container
|
||||
# Gen Version try to get version from tag or inut
|
||||
- name: Set output tags
|
||||
id: vars
|
||||
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
|
||||
- name: Gen Version
|
||||
id: gen_ver
|
||||
run: |
|
||||
if [[ $GITHUB_REF == 'refs/tags/'* ]]; then
|
||||
IMAGE_VER=${{ steps.vars.outputs.tag }}
|
||||
else
|
||||
IMAGE_VER=${{ github.event.inputs.IMAGE_VER }}
|
||||
fi
|
||||
if [[ $IMAGE_VER == '' ]]; then
|
||||
IMAGE_VER='test-vBeta'
|
||||
fi
|
||||
echo '::set-output name=image_ver::'$IMAGE_VER
|
||||
- name: Check Version
|
||||
run: |
|
||||
echo $GITHUB_REF
|
||||
echo ${{ steps.gen_ver.outputs.image_ver }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login in to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{env.REGISTRY}}
|
||||
username: ${{secrets.DOCKER_USER}}
|
||||
password: ${{secrets.DOCKER_PASS}}
|
||||
- name: Build and push docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest
|
||||
|
||||
- uses: snow-actions/line-notify@v1.1.0
|
||||
if: success()
|
||||
with:
|
||||
access_token: ${{ secrets.TOKEN_LINE }}
|
||||
message: |
|
||||
-Success✅✅✅
|
||||
Image: ${{env.IMAGE_NAME}}
|
||||
Version: ${{ steps.gen_ver.outputs.IMAGE_VER }}
|
||||
By: ${{ github.actor }}
|
||||
- uses: snow-actions/line-notify@v1.1.0
|
||||
if: failure()
|
||||
with:
|
||||
access_token: ${{ secrets.TOKEN_LINE }}
|
||||
message: |
|
||||
-Failure❌❌❌
|
||||
Image: ${{env.IMAGE_NAME}}
|
||||
Version: ${{ steps.gen_ver.outputs.IMAGE_VER }}
|
||||
By: ${{ github.actor }}
|
||||
|
|
@ -4,6 +4,8 @@ ENV PNPM_HOME="/pnpm"
|
|||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
RUN corepack enable
|
||||
RUN apt-get update && apt-get install -y openssl
|
||||
RUN pnpm i -g prisma
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
@ -11,6 +13,7 @@ COPY . .
|
|||
|
||||
FROM base AS deps
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
RUN pnpm prisma generate
|
||||
|
||||
FROM base AS build
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
|
@ -18,9 +21,13 @@ RUN pnpm prisma generate
|
|||
RUN pnpm run build
|
||||
|
||||
FROM base as prod
|
||||
|
||||
ENV NODE_ENV="production"
|
||||
|
||||
COPY --from=deps /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/dist /app/dist
|
||||
COPY --from=base /app/static /app/static
|
||||
|
||||
CMD ["pnpm", "run", "start"]
|
||||
RUN chmod u+x ./entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
|
|
|||
25
compose.yaml
Normal file
25
compose.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
- KC_URL=http://192.168.1.20:8080
|
||||
- KC_REALM=dev
|
||||
- KC_SERVICE_ACCOUNT_CLIENT_ID=
|
||||
- KC_SERVICE_ACCOUNT_SECRET=
|
||||
- APP_HOST=0.0.0.0
|
||||
- APP_PORT=3000
|
||||
- MINIO_HOST=192.168.1.20
|
||||
- MINIO_PORT=9000
|
||||
- MINIO_ACCESS_KEY=
|
||||
- MINIO_SECRET_KEY=
|
||||
- MINIO_BUCKET=jws-dev
|
||||
- ELASTICSEARCH_PROTOCOL=http
|
||||
- ELASTICSEARCH_HOST=192.168.1.20
|
||||
- ELASTICSEARCH_PORT=9200
|
||||
- ELASTICSEARCH_INDEX=jws-log-index
|
||||
- DATABASE_URL=postgresql://postgres:1234@192.168.1.20:5432/dev_1?schema=public
|
||||
4
entrypoint.sh
Normal file
4
entrypoint.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
pnpm prisma migrate deploy
|
||||
pnpm run start
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "template",
|
||||
"name": "jws-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "./dist/app.js",
|
||||
|
|
@ -24,12 +24,13 @@
|
|||
"@types/swagger-ui-express": "^4.1.6",
|
||||
"nodemon": "^3.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prisma": "^5.11.0",
|
||||
"prisma": "^5.12.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "5.11.0",
|
||||
"@elastic/elasticsearch": "^8.13.0",
|
||||
"@prisma/client": "5.12.1",
|
||||
"@tsoa/runtime": "^6.2.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
|
|
|
|||
105
pnpm-lock.yaml
generated
105
pnpm-lock.yaml
generated
|
|
@ -5,9 +5,12 @@ settings:
|
|||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@elastic/elasticsearch':
|
||||
specifier: ^8.13.0
|
||||
version: 8.13.0
|
||||
'@prisma/client':
|
||||
specifier: 5.11.0
|
||||
version: 5.11.0(prisma@5.11.0)
|
||||
specifier: 5.12.1
|
||||
version: 5.12.1(prisma@5.12.1)
|
||||
'@tsoa/runtime':
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0
|
||||
|
|
@ -56,8 +59,8 @@ devDependencies:
|
|||
specifier: ^3.2.5
|
||||
version: 3.2.5
|
||||
prisma:
|
||||
specifier: ^5.11.0
|
||||
version: 5.11.0
|
||||
specifier: ^5.12.1
|
||||
version: 5.12.1
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@20.12.2)(typescript@5.4.3)
|
||||
|
|
@ -74,6 +77,30 @@ packages:
|
|||
'@jridgewell/trace-mapping': 0.3.9
|
||||
dev: true
|
||||
|
||||
/@elastic/elasticsearch@8.13.0:
|
||||
resolution: {integrity: sha512-OAYgzqArPqgDaIJ1yT0RX31YCgr1lleo53zL+36i23PFjHu08CA6Uq+BmBzEV05yEidl+ILPdeSfF3G8hPG/JQ==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
'@elastic/transport': 8.5.0
|
||||
tslib: 2.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@elastic/transport@8.5.0:
|
||||
resolution: {integrity: sha512-T+zSUHXBfrqlj/E9pJiaEgKoTdGykBCohzNBt6omDfI6EQtaNT240oMO03oXo35T8rwrCVonSMSoedbmToncVA==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
hpagent: 1.2.0
|
||||
ms: 2.1.3
|
||||
secure-json-parse: 2.7.0
|
||||
tslib: 2.6.2
|
||||
undici: 6.11.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@hapi/accept@6.0.3:
|
||||
resolution: {integrity: sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==}
|
||||
dependencies:
|
||||
|
|
@ -339,8 +366,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@prisma/client@5.11.0(prisma@5.11.0):
|
||||
resolution: {integrity: sha512-SWshvS5FDXvgJKM/a0y9nDC1rqd7KG0Q6ZVzd+U7ZXK5soe73DJxJJgbNBt2GNXOa+ysWB4suTpdK5zfFPhwiw==}
|
||||
/@prisma/client@5.12.1(prisma@5.12.1):
|
||||
resolution: {integrity: sha512-6/JnizEdlSBxDIdiLbrBdMW5NqDxOmhXAJaNXiPpgzAPr/nLZResT6MMpbOHLo5yAbQ1Vv5UU8PTPRzb0WIxdA==}
|
||||
engines: {node: '>=16.13'}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
|
|
@ -349,35 +376,35 @@ packages:
|
|||
prisma:
|
||||
optional: true
|
||||
dependencies:
|
||||
prisma: 5.11.0
|
||||
prisma: 5.12.1
|
||||
dev: false
|
||||
|
||||
/@prisma/debug@5.11.0:
|
||||
resolution: {integrity: sha512-N6yYr3AbQqaiUg+OgjkdPp3KPW1vMTAgtKX6+BiB/qB2i1TjLYCrweKcUjzOoRM5BriA4idrkTej9A9QqTfl3A==}
|
||||
/@prisma/debug@5.12.1:
|
||||
resolution: {integrity: sha512-kd/wNsR0klrv79o1ITsbWxYyh4QWuBidvxsXSParPsYSu0ircUmNk3q4ojsgNc3/81b0ozg76iastOG43tbf8A==}
|
||||
|
||||
/@prisma/engines-version@5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102:
|
||||
resolution: {integrity: sha512-WXCuyoymvrS4zLz4wQagSsc3/nE6CHy8znyiMv8RKazKymOMd5o9FP5RGwGHAtgoxd+aB/BWqxuP/Ckfu7/3MA==}
|
||||
/@prisma/engines-version@5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab:
|
||||
resolution: {integrity: sha512-6yvO8s80Tym61aB4QNtYZfWVmE3pwqe807jEtzm8C5VDe7nw8O1FGX3TXUaXmWV0fQTIAfRbeL2Gwrndabp/0g==}
|
||||
|
||||
/@prisma/engines@5.11.0:
|
||||
resolution: {integrity: sha512-gbrpQoBTYWXDRqD+iTYMirDlF9MMlQdxskQXbhARhG6A/uFQjB7DZMYocMQLoiZXO/IskfDOZpPoZE8TBQKtEw==}
|
||||
/@prisma/engines@5.12.1:
|
||||
resolution: {integrity: sha512-HQDdglLw2bZR/TXD2Y+YfDMvi5Q8H+acbswqOsWyq9pPjBLYJ6gzM+ptlTU/AV6tl0XSZLU1/7F4qaWa8bqpJA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@prisma/debug': 5.11.0
|
||||
'@prisma/engines-version': 5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102
|
||||
'@prisma/fetch-engine': 5.11.0
|
||||
'@prisma/get-platform': 5.11.0
|
||||
'@prisma/debug': 5.12.1
|
||||
'@prisma/engines-version': 5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab
|
||||
'@prisma/fetch-engine': 5.12.1
|
||||
'@prisma/get-platform': 5.12.1
|
||||
|
||||
/@prisma/fetch-engine@5.11.0:
|
||||
resolution: {integrity: sha512-994viazmHTJ1ymzvWugXod7dZ42T2ROeFuH6zHPcUfp/69+6cl5r9u3NFb6bW8lLdNjwLYEVPeu3hWzxpZeC0w==}
|
||||
/@prisma/fetch-engine@5.12.1:
|
||||
resolution: {integrity: sha512-qSs3KcX1HKcea1A+hlJVK/ljj0PNIUHDxAayGMvgJBqmaN32P9tCidlKz1EGv6WoRFICYnk3Dd/YFLBwnFIozA==}
|
||||
dependencies:
|
||||
'@prisma/debug': 5.11.0
|
||||
'@prisma/engines-version': 5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102
|
||||
'@prisma/get-platform': 5.11.0
|
||||
'@prisma/debug': 5.12.1
|
||||
'@prisma/engines-version': 5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab
|
||||
'@prisma/get-platform': 5.12.1
|
||||
|
||||
/@prisma/get-platform@5.11.0:
|
||||
resolution: {integrity: sha512-rxtHpMLxNTHxqWuGOLzR2QOyQi79rK1u1XYAVLZxDGTLz/A+uoDnjz9veBFlicrpWjwuieM4N6jcnjj/DDoidw==}
|
||||
/@prisma/get-platform@5.12.1:
|
||||
resolution: {integrity: sha512-pgIR+pSvhYHiUcqXVEZS31NrFOTENC9yFUdEAcx7cdQBoZPmHVjtjN4Ss6NzVDMYPrKJJ51U14EhEoeuBlMioQ==}
|
||||
dependencies:
|
||||
'@prisma/debug': 5.11.0
|
||||
'@prisma/debug': 5.12.1
|
||||
|
||||
/@tsconfig/node10@1.0.11:
|
||||
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
|
||||
|
|
@ -892,7 +919,6 @@ packages:
|
|||
dependencies:
|
||||
ms: 2.1.2
|
||||
supports-color: 5.5.0
|
||||
dev: true
|
||||
|
||||
/decode-uri-component@0.2.2:
|
||||
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
||||
|
|
@ -1322,7 +1348,6 @@ packages:
|
|||
/has-flag@3.0.0:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/has-property-descriptors@1.0.2:
|
||||
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
|
||||
|
|
@ -1354,6 +1379,11 @@ packages:
|
|||
function-bind: 1.1.2
|
||||
dev: false
|
||||
|
||||
/hpagent@1.2.0:
|
||||
resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==}
|
||||
engines: {node: '>=14'}
|
||||
dev: false
|
||||
|
||||
/http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
|
@ -1715,7 +1745,6 @@ packages:
|
|||
|
||||
/ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
|
@ -1832,13 +1861,13 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/prisma@5.11.0:
|
||||
resolution: {integrity: sha512-KCLiug2cs0Je7kGkQBN9jDWoZ90ogE/kvZTUTgz2h94FEo8pczCkPH7fPNXkD1sGU7Yh65risGGD1HQ5DF3r3g==}
|
||||
/prisma@5.12.1:
|
||||
resolution: {integrity: sha512-SkMnb6wyIxTv9ACqiHBI2u9gD6y98qXRoCoLEnZsF6yee5Qg828G+ARrESN+lQHdw4maSZFFSBPPDpvSiVTo0Q==}
|
||||
engines: {node: '>=16.13'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@prisma/engines': 5.11.0
|
||||
'@prisma/engines': 5.12.1
|
||||
|
||||
/promise.any@2.0.6:
|
||||
resolution: {integrity: sha512-Ew/MrPtTjiHnnki0AA2hS2o65JaZ5n+5pp08JSyWWUdeOGF4F41P+Dn+rdqnaOV/FTxhR6eBDX412luwn3th9g==}
|
||||
|
|
@ -1963,6 +1992,10 @@ packages:
|
|||
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
|
||||
dev: false
|
||||
|
||||
/secure-json-parse@2.7.0:
|
||||
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||
dev: false
|
||||
|
||||
/semver@7.6.0:
|
||||
resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -2165,7 +2198,6 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
dev: true
|
||||
|
||||
/swagger-ui-dist@5.13.0:
|
||||
resolution: {integrity: sha512-uaWhh6j18IIs5tOX0arvIBnVINAzpTXaQXkr7qAk8zoupegJVg0UU/5+S/FgsgVCnzVsJ9d7QLjIxkswEeTg0Q==}
|
||||
|
|
@ -2242,6 +2274,10 @@ packages:
|
|||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/tslib@2.6.2:
|
||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||
dev: false
|
||||
|
||||
/tsoa@6.2.0:
|
||||
resolution: {integrity: sha512-EX/RyoU+4hD1rLM5NjYG+I7lEhqx1yuLgcHs/gyWQpkX/RL9cVR9hFA9LKQrK6PE+WTg1SEahn1MK3l/+6pVKw==}
|
||||
engines: {node: '>=18.0.0', yarn: '>=1.9.4'}
|
||||
|
|
@ -2334,6 +2370,11 @@ packages:
|
|||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
/undici@6.11.1:
|
||||
resolution: {integrity: sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==}
|
||||
engines: {node: '>=18.0'}
|
||||
dev: false
|
||||
|
||||
/universalify@2.0.1:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
|
|
|||
458
prisma/migrations/20240409061231_init/migration.sql
Normal file
458
prisma/migrations/20240409061231_init/migration.sql
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
-- CreateEnum
|
||||
CREATE TYPE "Status" AS ENUM ('CREATED', 'ACTIVE', 'INACTIVE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserType" AS ENUM ('USER', 'MESSENGER', 'DELEGATE', 'AGENCY');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Province" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameEN" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Province_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "District" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameEN" TEXT NOT NULL,
|
||||
"provinceId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "District_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SubDistrict" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameEN" TEXT NOT NULL,
|
||||
"zipCode" TEXT NOT NULL,
|
||||
"districtId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SubDistrict_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Branch" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"taxNo" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameEN" TEXT NOT NULL,
|
||||
"address" TEXT NOT NULL,
|
||||
"addressEN" TEXT NOT NULL,
|
||||
"provinceId" TEXT,
|
||||
"districtId" TEXT,
|
||||
"subDistrictId" TEXT,
|
||||
"zipCode" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"telephoneNo" TEXT NOT NULL,
|
||||
"latitude" TEXT NOT NULL,
|
||||
"longitude" TEXT NOT NULL,
|
||||
"isHeadOffice" BOOLEAN NOT NULL DEFAULT false,
|
||||
"headOfficeId" TEXT,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Branch_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BranchContact" (
|
||||
"id" TEXT NOT NULL,
|
||||
"telephoneNo" TEXT NOT NULL,
|
||||
"lineId" TEXT NOT NULL,
|
||||
"branchId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BranchContact_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BranchUser" (
|
||||
"id" TEXT NOT NULL,
|
||||
"branchId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BranchUser_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"keycloakId" TEXT NOT NULL,
|
||||
"code" TEXT,
|
||||
"firstName" TEXT NOT NULL,
|
||||
"firstNameEN" TEXT NOT NULL,
|
||||
"lastName" TEXT NOT NULL,
|
||||
"lastNameEN" TEXT NOT NULL,
|
||||
"gender" TEXT NOT NULL,
|
||||
"address" TEXT NOT NULL,
|
||||
"addressEN" TEXT NOT NULL,
|
||||
"provinceId" TEXT,
|
||||
"districtId" TEXT,
|
||||
"subDistrictId" TEXT,
|
||||
"zipCode" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"telephoneNo" TEXT NOT NULL,
|
||||
"registrationNo" TEXT,
|
||||
"startDate" TIMESTAMP(3),
|
||||
"retireDate" TIMESTAMP(3),
|
||||
"userType" "UserType" NOT NULL,
|
||||
"userRole" TEXT NOT NULL,
|
||||
"discountCondition" TEXT,
|
||||
"licenseNo" TEXT,
|
||||
"licenseIssueDate" TIMESTAMP(3),
|
||||
"licenseExpireDate" TIMESTAMP(3),
|
||||
"sourceNationality" TEXT,
|
||||
"importNationality" TEXT,
|
||||
"trainingPlace" TEXT,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Customer" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"customerType" TEXT NOT NULL,
|
||||
"customerName" TEXT NOT NULL,
|
||||
"customerNameEN" TEXT NOT NULL,
|
||||
"imageUrl" TEXT,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CustomerBranch" (
|
||||
"id" TEXT NOT NULL,
|
||||
"branchNo" TEXT NOT NULL,
|
||||
"legalPersonNo" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameEN" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"taxNo" TEXT NOT NULL,
|
||||
"registerName" TEXT NOT NULL,
|
||||
"registerDate" TIMESTAMP(3) NOT NULL,
|
||||
"authorizedCapital" TEXT NOT NULL,
|
||||
"address" TEXT NOT NULL,
|
||||
"addressEN" TEXT NOT NULL,
|
||||
"provinceId" TEXT,
|
||||
"districtId" TEXT,
|
||||
"subDistrictId" TEXT,
|
||||
"zipCode" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"telephoneNo" TEXT NOT NULL,
|
||||
"latitude" TEXT NOT NULL,
|
||||
"longitude" TEXT NOT NULL,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CustomerBranch_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Employee" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"nrcNo" TEXT NOT NULL,
|
||||
"firstName" TEXT NOT NULL,
|
||||
"firstNameEN" TEXT NOT NULL,
|
||||
"lastName" TEXT NOT NULL,
|
||||
"lastNameEN" TEXT NOT NULL,
|
||||
"dateOfBirth" TIMESTAMP(3) NOT NULL,
|
||||
"gender" TEXT NOT NULL,
|
||||
"nationality" TEXT NOT NULL,
|
||||
"address" TEXT NOT NULL,
|
||||
"addressEN" TEXT NOT NULL,
|
||||
"provinceId" TEXT,
|
||||
"districtId" TEXT,
|
||||
"subDistrictId" TEXT,
|
||||
"zipCode" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"telephoneNo" TEXT NOT NULL,
|
||||
"arrivalBarricade" TEXT NOT NULL,
|
||||
"arrivalCardNo" TEXT NOT NULL,
|
||||
"customerBranchId" TEXT,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Employee_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmployeeCheckup" (
|
||||
"id" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"checkupResult" TEXT NOT NULL,
|
||||
"checkupType" TEXT NOT NULL,
|
||||
"provinceId" TEXT,
|
||||
"hospitalName" TEXT NOT NULL,
|
||||
"remark" TEXT NOT NULL,
|
||||
"medicalBenefitScheme" TEXT NOT NULL,
|
||||
"insuranceCompany" TEXT NOT NULL,
|
||||
"coverageStartDate" TIMESTAMP(3) NOT NULL,
|
||||
"coverageExpireDate" TIMESTAMP(3) NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "EmployeeCheckup_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmployeeWork" (
|
||||
"id" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"ownerName" TEXT NOT NULL,
|
||||
"positionName" TEXT NOT NULL,
|
||||
"jobType" TEXT NOT NULL,
|
||||
"workplace" TEXT NOT NULL,
|
||||
"workPermitNo" TEXT NOT NULL,
|
||||
"workPermitIssuDate" TIMESTAMP(3) NOT NULL,
|
||||
"workPermitExpireDate" TIMESTAMP(3) NOT NULL,
|
||||
"workEndDate" TIMESTAMP(3) NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "EmployeeWork_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmployeeOtherInfo" (
|
||||
"id" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"citizenId" TEXT NOT NULL,
|
||||
"fatherFullName" TEXT NOT NULL,
|
||||
"motherFullName" TEXT NOT NULL,
|
||||
"birthPlace" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "EmployeeOtherInfo_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Service" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"detail" TEXT NOT NULL,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Service_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Work" (
|
||||
"id" TEXT NOT NULL,
|
||||
"order" INTEGER NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Work_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "WorkProduct" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "WorkProduct_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProductGroup" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"detail" TEXT NOT NULL,
|
||||
"remark" TEXT NOT NULL,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductGroup_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProductType" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"detail" TEXT NOT NULL,
|
||||
"remark" TEXT NOT NULL,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProductType_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Product" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"detail" TEXT NOT NULL,
|
||||
"process" TEXT NOT NULL,
|
||||
"price" INTEGER NOT NULL,
|
||||
"agentPrice" INTEGER NOT NULL,
|
||||
"serviceCharge" INTEGER NOT NULL,
|
||||
"imageUrl" TEXT NOT NULL,
|
||||
"status" "Status" NOT NULL DEFAULT 'CREATED',
|
||||
"productTypeId" TEXT,
|
||||
"productGroupId" TEXT,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "District" ADD CONSTRAINT "District_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SubDistrict" ADD CONSTRAINT "SubDistrict_districtId_fkey" FOREIGN KEY ("districtId") REFERENCES "District"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_districtId_fkey" FOREIGN KEY ("districtId") REFERENCES "District"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_subDistrictId_fkey" FOREIGN KEY ("subDistrictId") REFERENCES "SubDistrict"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Branch" ADD CONSTRAINT "Branch_headOfficeId_fkey" FOREIGN KEY ("headOfficeId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BranchContact" ADD CONSTRAINT "BranchContact_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BranchUser" ADD CONSTRAINT "BranchUser_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BranchUser" ADD CONSTRAINT "BranchUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "User" ADD CONSTRAINT "User_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "User" ADD CONSTRAINT "User_districtId_fkey" FOREIGN KEY ("districtId") REFERENCES "District"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "User" ADD CONSTRAINT "User_subDistrictId_fkey" FOREIGN KEY ("subDistrictId") REFERENCES "SubDistrict"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_districtId_fkey" FOREIGN KEY ("districtId") REFERENCES "District"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_subDistrictId_fkey" FOREIGN KEY ("subDistrictId") REFERENCES "SubDistrict"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Employee" ADD CONSTRAINT "Employee_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Employee" ADD CONSTRAINT "Employee_districtId_fkey" FOREIGN KEY ("districtId") REFERENCES "District"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Employee" ADD CONSTRAINT "Employee_subDistrictId_fkey" FOREIGN KEY ("subDistrictId") REFERENCES "SubDistrict"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Employee" ADD CONSTRAINT "Employee_customerBranchId_fkey" FOREIGN KEY ("customerBranchId") REFERENCES "CustomerBranch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeCheckup" ADD CONSTRAINT "EmployeeCheckup_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeCheckup" ADD CONSTRAINT "EmployeeCheckup_provinceId_fkey" FOREIGN KEY ("provinceId") REFERENCES "Province"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeWork" ADD CONSTRAINT "EmployeeWork_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeOtherInfo" ADD CONSTRAINT "EmployeeOtherInfo_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Work" ADD CONSTRAINT "Work_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "WorkProduct" ADD CONSTRAINT "WorkProduct_workId_fkey" FOREIGN KEY ("workId") REFERENCES "Work"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Product" ADD CONSTRAINT "Product_productTypeId_fkey" FOREIGN KEY ("productTypeId") REFERENCES "ProductType"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Product" ADD CONSTRAINT "Product_productGroupId_fkey" FOREIGN KEY ("productGroupId") REFERENCES "ProductGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `imageUrl` on the `Customer` table. All the data in the column will be lost.
|
||||
- Changed the type of `customerType` on the `Customer` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
|
||||
*/
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CustomerType" AS ENUM ('CORP', 'PERS');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Customer" DROP COLUMN "imageUrl",
|
||||
DROP COLUMN "customerType",
|
||||
ADD COLUMN "customerType" "CustomerType" NOT NULL;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "birtDate" TIMESTAMP(3),
|
||||
ADD COLUMN "responsibleArea" TEXT;
|
||||
9
prisma/migrations/20240410053228_fix_typo/migration.sql
Normal file
9
prisma/migrations/20240410053228_fix_typo/migration.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `birtDate` on the `User` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" DROP COLUMN "birtDate",
|
||||
ADD COLUMN "birthDate" TIMESTAMP(3);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `keycloakId` on the `User` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" DROP COLUMN "keycloakId";
|
||||
11
prisma/migrations/20240417041541_move_field/migration.sql
Normal file
11
prisma/migrations/20240417041541_move_field/migration.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `lineId` on the `BranchContact` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Branch" ADD COLUMN "lineId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "BranchContact" DROP COLUMN "lineId";
|
||||
119
prisma/migrations/20240417063829_update_table/migration.sql
Normal file
119
prisma/migrations/20240417063829_update_table/migration.sql
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `telephoneNo` on the `Branch` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Branch" DROP COLUMN "telephoneNo";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Menu" (
|
||||
"id" TEXT NOT NULL,
|
||||
"caption" TEXT NOT NULL,
|
||||
"captionEN" TEXT NOT NULL,
|
||||
"menuType" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"parentId" TEXT,
|
||||
|
||||
CONSTRAINT "Menu_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RoleMenuPermission" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userRole" TEXT NOT NULL,
|
||||
"permission" TEXT NOT NULL,
|
||||
"menuId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "RoleMenuPermission_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserMenuPermission" (
|
||||
"id" TEXT NOT NULL,
|
||||
"permission" TEXT NOT NULL,
|
||||
"menuId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "UserMenuPermission_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "MenuComponent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"componentId" TEXT NOT NULL,
|
||||
"componentTag" TEXT NOT NULL,
|
||||
"menuId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "MenuComponent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RoleMenuComponentPermission" (
|
||||
"id" TEXT NOT NULL,
|
||||
"componentId" TEXT NOT NULL,
|
||||
"componentTag" TEXT NOT NULL,
|
||||
"menuComponentId" TEXT NOT NULL,
|
||||
"permission" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "RoleMenuComponentPermission_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserMenuComponentPermission" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"menuComponentId" TEXT NOT NULL,
|
||||
"permission" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateBy" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "UserMenuComponentPermission_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Menu" ADD CONSTRAINT "Menu_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Menu"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RoleMenuPermission" ADD CONSTRAINT "RoleMenuPermission_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserMenuPermission" ADD CONSTRAINT "UserMenuPermission_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserMenuPermission" ADD CONSTRAINT "UserMenuPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MenuComponent" ADD CONSTRAINT "MenuComponent_menuId_fkey" FOREIGN KEY ("menuId") REFERENCES "Menu"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RoleMenuComponentPermission" ADD CONSTRAINT "RoleMenuComponentPermission_menuComponentId_fkey" FOREIGN KEY ("menuComponentId") REFERENCES "MenuComponent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserMenuComponentPermission" ADD CONSTRAINT "UserMenuComponentPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserMenuComponentPermission" ADD CONSTRAINT "UserMenuComponentPermission_menuComponentId_fkey" FOREIGN KEY ("menuComponentId") REFERENCES "MenuComponent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Branch" ADD COLUMN "contactName" TEXT;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "checkpoint" TEXT,
|
||||
ADD COLUMN "checkpointEN" TEXT;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `username` to the `User` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "username" TEXT NOT NULL;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `componentId` on the `RoleMenuComponentPermission` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `componentTag` on the `RoleMenuComponentPermission` table. All the data in the column will be lost.
|
||||
- Added the required column `userRole` to the `RoleMenuComponentPermission` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "RoleMenuComponentPermission" DROP COLUMN "componentId",
|
||||
DROP COLUMN "componentTag",
|
||||
ADD COLUMN "userRole" TEXT NOT NULL;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Branch" ADD COLUMN "telephoneHq" TEXT;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `telephoneHq` on table `Branch` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Branch" ALTER COLUMN "telephoneHq" SET NOT NULL;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `telephoneHq` on the `Branch` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Branch" DROP COLUMN "telephoneHq",
|
||||
ADD COLUMN "telephoneNo" TEXT;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `telephoneNo` on table `Branch` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Branch" ALTER COLUMN "telephoneNo" SET NOT NULL;
|
||||
|
|
@ -7,9 +7,112 @@ datasource db {
|
|||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Menu {
|
||||
id String @id @default(uuid())
|
||||
|
||||
caption String
|
||||
captionEN String
|
||||
menuType String
|
||||
url String
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
parent Menu? @relation(name: "MenuRelation", fields: [parentId], references: [id])
|
||||
parentId String?
|
||||
|
||||
children Menu[] @relation(name: "MenuRelation")
|
||||
roleMenuPermission RoleMenuPermission[]
|
||||
userMenuPermission UserMenuPermission[]
|
||||
userComponent MenuComponent[]
|
||||
}
|
||||
|
||||
model RoleMenuPermission {
|
||||
id String @id @default(uuid())
|
||||
|
||||
userRole String
|
||||
permission String
|
||||
|
||||
menu Menu @relation(fields: [menuId], references: [id])
|
||||
menuId String
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model UserMenuPermission {
|
||||
id String @id @default(uuid())
|
||||
|
||||
permission String
|
||||
|
||||
menu Menu @relation(fields: [menuId], references: [id])
|
||||
menuId String
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model MenuComponent {
|
||||
id String @id @default(uuid())
|
||||
|
||||
componentId String
|
||||
componentTag String
|
||||
|
||||
menu Menu @relation(fields: [menuId], references: [id])
|
||||
menuId String
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
updatedAt DateTime @updatedAt
|
||||
roleMenuComponentPermission RoleMenuComponentPermission[]
|
||||
userMennuComponentPermission UserMenuComponentPermission[]
|
||||
}
|
||||
|
||||
model RoleMenuComponentPermission {
|
||||
id String @id @default(uuid())
|
||||
|
||||
userRole String
|
||||
permission String
|
||||
|
||||
menuComponent MenuComponent @relation(fields: [menuComponentId], references: [id])
|
||||
menuComponentId String
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model UserMenuComponentPermission {
|
||||
id String @id @default(uuid())
|
||||
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
menuComponent MenuComponent @relation(fields: [menuComponentId], references: [id])
|
||||
menuComponentId String
|
||||
|
||||
permission String
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Province {
|
||||
id String @id @default(uuid())
|
||||
nameTH String
|
||||
name String
|
||||
nameEN String
|
||||
|
||||
createdBy String?
|
||||
|
|
@ -27,7 +130,7 @@ model Province {
|
|||
|
||||
model District {
|
||||
id String @id @default(uuid())
|
||||
nameTH String
|
||||
name String
|
||||
nameEN String
|
||||
|
||||
provinceId String
|
||||
|
|
@ -47,7 +150,7 @@ model District {
|
|||
|
||||
model SubDistrict {
|
||||
id String @id @default(uuid())
|
||||
nameTH String
|
||||
name String
|
||||
nameEN String
|
||||
zipCode String
|
||||
|
||||
|
|
@ -65,14 +168,21 @@ model SubDistrict {
|
|||
employee Employee[]
|
||||
}
|
||||
|
||||
enum Status {
|
||||
CREATED
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
}
|
||||
|
||||
model Branch {
|
||||
id String @id @default(uuid())
|
||||
code String
|
||||
taxNo String
|
||||
nameTH String
|
||||
nameEN String
|
||||
addressTH String
|
||||
addressEN String
|
||||
id String @id @default(uuid())
|
||||
code String
|
||||
taxNo String
|
||||
name String
|
||||
nameEN String
|
||||
address String
|
||||
addressEN String
|
||||
telephoneNo String
|
||||
|
||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||
provinceId String?
|
||||
|
|
@ -86,7 +196,8 @@ model Branch {
|
|||
zipCode String
|
||||
|
||||
email String
|
||||
telephoneNo String
|
||||
contactName String?
|
||||
lineId String?
|
||||
|
||||
latitude String
|
||||
longitude String
|
||||
|
|
@ -96,7 +207,7 @@ model Branch {
|
|||
headOffice Branch? @relation(name: "HeadOfficeRelation", fields: [headOfficeId], references: [id])
|
||||
headOfficeId String?
|
||||
|
||||
status String?
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
|
|
@ -109,10 +220,8 @@ model Branch {
|
|||
}
|
||||
|
||||
model BranchContact {
|
||||
id String @id @default(uuid())
|
||||
telephoneNo String
|
||||
lineId String
|
||||
qrCodeImageUrl String?
|
||||
id String @id @default(uuid())
|
||||
telephoneNo String
|
||||
|
||||
branch Branch @relation(fields: [branchId], references: [id], onDelete: Cascade)
|
||||
branchId String
|
||||
|
|
@ -138,16 +247,25 @@ model BranchUser {
|
|||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
enum UserType {
|
||||
USER
|
||||
MESSENGER
|
||||
DELEGATE
|
||||
AGENCY
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
|
||||
code String
|
||||
firstNameTH String
|
||||
code String?
|
||||
firstName String
|
||||
firstNameEN String
|
||||
lastNameTH String
|
||||
lastName String
|
||||
lastNameEN String
|
||||
username String
|
||||
gender String
|
||||
|
||||
addressTH String
|
||||
address String
|
||||
addressEN String
|
||||
|
||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||
|
|
@ -164,46 +282,56 @@ model User {
|
|||
email String
|
||||
telephoneNo String
|
||||
|
||||
registrationNo String
|
||||
registrationNo String?
|
||||
|
||||
startDate DateTime
|
||||
retireDate DateTime
|
||||
startDate DateTime?
|
||||
retireDate DateTime?
|
||||
|
||||
profileImageUrl String?
|
||||
checkpoint String?
|
||||
checkpointEN String?
|
||||
|
||||
userType String
|
||||
userType UserType
|
||||
userRole String
|
||||
|
||||
discountCondition String
|
||||
discountCondition String?
|
||||
|
||||
licenseNo String
|
||||
licenseIssueDate DateTime
|
||||
licenseExpireDate DateTime
|
||||
licenseNo String?
|
||||
licenseIssueDate DateTime?
|
||||
licenseExpireDate DateTime?
|
||||
|
||||
sourceNationality String
|
||||
importNationality String
|
||||
sourceNationality String?
|
||||
importNationality String?
|
||||
|
||||
trainingPlace String
|
||||
trainingPlace String?
|
||||
responsibleArea String?
|
||||
|
||||
status String?
|
||||
birthDate DateTime?
|
||||
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
branch BranchUser[]
|
||||
branch BranchUser[]
|
||||
userMenuPermission UserMenuPermission[]
|
||||
userMenuComponentPermission UserMenuComponentPermission[]
|
||||
}
|
||||
|
||||
enum CustomerType {
|
||||
CORP
|
||||
PERS
|
||||
}
|
||||
|
||||
model Customer {
|
||||
id String @id @default(uuid())
|
||||
id String @id @default(uuid())
|
||||
code String
|
||||
customerType String
|
||||
customerNameTH String
|
||||
customerType CustomerType
|
||||
customerName String
|
||||
customerNameEN String
|
||||
imageUrl String?
|
||||
|
||||
status String?
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
|
|
@ -218,7 +346,7 @@ model CustomerBranch {
|
|||
branchNo String
|
||||
legalPersonNo String
|
||||
|
||||
nameTH String
|
||||
name String
|
||||
nameEN String
|
||||
|
||||
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||
|
|
@ -229,6 +357,7 @@ model CustomerBranch {
|
|||
registerDate DateTime
|
||||
authorizedCapital String
|
||||
|
||||
address String
|
||||
addressEN String
|
||||
|
||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||
|
|
@ -248,6 +377,8 @@ model CustomerBranch {
|
|||
latitude String
|
||||
longitude String
|
||||
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
|
|
@ -260,13 +391,17 @@ model Employee {
|
|||
id String @id @default(uuid())
|
||||
|
||||
code String
|
||||
fullNameTH String
|
||||
fullNameEN String
|
||||
nrcNo String
|
||||
firstName String
|
||||
firstNameEN String
|
||||
lastName String
|
||||
lastNameEN String
|
||||
|
||||
dateOfBirth DateTime
|
||||
gender String
|
||||
nationality String
|
||||
|
||||
addressTH String
|
||||
address String
|
||||
addressEN String
|
||||
|
||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||
|
|
@ -285,12 +420,11 @@ model Employee {
|
|||
|
||||
arrivalBarricade String
|
||||
arrivalCardNo String
|
||||
profileImageUrl String
|
||||
|
||||
customerBranch CustomerBranch? @relation(fields: [customerBranchId], references: [id], onDelete: SetNull)
|
||||
customerBranchId String?
|
||||
|
||||
status String?
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
|
|
@ -299,7 +433,7 @@ model Employee {
|
|||
|
||||
employeeCheckup EmployeeCheckup[]
|
||||
employeeWork EmployeeWork[]
|
||||
EmployeeOtherInfo EmployeeOtherInfo[]
|
||||
employeeOtherInfo EmployeeOtherInfo[]
|
||||
}
|
||||
|
||||
model EmployeeCheckup {
|
||||
|
|
@ -368,11 +502,11 @@ model EmployeeOtherInfo {
|
|||
model Service {
|
||||
id String @id @default(uuid())
|
||||
|
||||
code String
|
||||
name String
|
||||
detail String
|
||||
imageUrl String
|
||||
status String?
|
||||
code String
|
||||
name String
|
||||
detail String
|
||||
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
|
|
@ -384,12 +518,14 @@ model Service {
|
|||
model Work {
|
||||
id String @id @default(uuid())
|
||||
|
||||
order String
|
||||
order Int
|
||||
name String
|
||||
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
serviceId String
|
||||
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updateBy String?
|
||||
|
|
@ -416,7 +552,8 @@ model ProductGroup {
|
|||
name String
|
||||
detail String
|
||||
remark String
|
||||
status String?
|
||||
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
|
|
@ -432,7 +569,8 @@ model ProductType {
|
|||
name String
|
||||
detail String
|
||||
remark String
|
||||
status String?
|
||||
|
||||
status Status @default(CREATED)
|
||||
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
|
|
@ -453,7 +591,8 @@ model Product {
|
|||
agentPrice Int
|
||||
serviceCharge Int
|
||||
imageUrl String
|
||||
status String?
|
||||
|
||||
status Status @default(CREATED)
|
||||
|
||||
productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull)
|
||||
productTypeId String?
|
||||
|
|
|
|||
12
src/app.ts
12
src/app.ts
|
|
@ -5,6 +5,7 @@ import swaggerUi from "swagger-ui-express";
|
|||
import swaggerDocument from "./swagger.json";
|
||||
import error from "./middlewares/error";
|
||||
import { RegisterRoutes } from "./routes";
|
||||
import logMiddleware from "./middlewares/log";
|
||||
|
||||
const APP_HOST = process.env.APP_HOST || "0.0.0.0";
|
||||
const APP_PORT = +(process.env.APP_PORT || 3000);
|
||||
|
|
@ -15,11 +16,22 @@ const APP_PORT = +(process.env.APP_PORT || 3000);
|
|||
app.use(cors());
|
||||
app.use(json());
|
||||
app.use(urlencoded({ extended: true }));
|
||||
|
||||
app.use(logMiddleware);
|
||||
|
||||
app.use("/", express.static("static"));
|
||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
||||
|
||||
RegisterRoutes(app);
|
||||
|
||||
app.get("*", (_, res) =>
|
||||
res.status(404).send({
|
||||
status: 404,
|
||||
message: "Route not found.",
|
||||
devMessage: "unknown_url",
|
||||
}),
|
||||
);
|
||||
|
||||
app.use(error);
|
||||
|
||||
app.listen(APP_PORT, APP_HOST, () => console.log(`Listening on: http://localhost:${APP_PORT}`));
|
||||
|
|
|
|||
56
src/controllers/address-controller.ts
Normal file
56
src/controllers/address-controller.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { Controller, Get, Path, Route, Tags } from "tsoa";
|
||||
import prisma from "../db";
|
||||
|
||||
@Route("api/address")
|
||||
@Tags("Address")
|
||||
export class AddressController extends Controller {
|
||||
@Get("province")
|
||||
async getProvince() {
|
||||
return await prisma.province.findMany();
|
||||
}
|
||||
|
||||
@Get("province/{provinceId}")
|
||||
async getProvinceById(@Path() provinceId: string) {
|
||||
return await prisma.province.findFirst({
|
||||
where: { id: provinceId },
|
||||
});
|
||||
}
|
||||
|
||||
@Get("province/{provinceId}/district")
|
||||
async getDistrictOfProvince(@Path() provinceId: string) {
|
||||
return await prisma.district.findMany({
|
||||
where: { provinceId },
|
||||
});
|
||||
}
|
||||
|
||||
@Get("district")
|
||||
async getDistrict() {
|
||||
return await prisma.district.findMany();
|
||||
}
|
||||
|
||||
@Get("district/{districtId}")
|
||||
async getDistrictOfId(@Path() districtId: string) {
|
||||
return await prisma.province.findFirst({
|
||||
where: { id: districtId },
|
||||
});
|
||||
}
|
||||
|
||||
@Get("district/{districtId}/sub-district")
|
||||
async getSubDistrictOfDistrict(@Path() districtId: string) {
|
||||
return await prisma.subDistrict.findMany({
|
||||
where: { districtId },
|
||||
});
|
||||
}
|
||||
|
||||
@Get("sub-district")
|
||||
async getSubDistrict() {
|
||||
return await prisma.subDistrict.findMany();
|
||||
}
|
||||
|
||||
@Get("sub-district/{subDistrictId}")
|
||||
async getSubDistrictOfId(@Path() subDistrictId: string) {
|
||||
return await prisma.subDistrict.findFirst({
|
||||
where: { id: subDistrictId },
|
||||
});
|
||||
}
|
||||
}
|
||||
132
src/controllers/branch-contact-controller.ts
Normal file
132
src/controllers/branch-contact-controller.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Put,
|
||||
Path,
|
||||
Post,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
|
||||
import prisma from "../db";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
|
||||
type BranchContactCreate = {
|
||||
telephoneNo: string;
|
||||
};
|
||||
|
||||
type BranchContactUpdate = {
|
||||
telephoneNo?: string;
|
||||
};
|
||||
|
||||
@Route("api/branch/{branchId}/contact")
|
||||
@Tags("Branch Contact")
|
||||
@Security("keycloak")
|
||||
export class BranchContactController extends Controller {
|
||||
@Get()
|
||||
async getBranchContact(
|
||||
@Path() branchId: string,
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.branchContact.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
where: { branchId },
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.branchContact.count({ where: { branchId } }),
|
||||
]);
|
||||
|
||||
return {
|
||||
result,
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("{contactId}")
|
||||
async getBranchContactById(@Path() branchId: string, @Path() contactId: string) {
|
||||
const record = await prisma.branchContact.findFirst({ where: { id: contactId, branchId } });
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Branch contact cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createBranchContact(
|
||||
@Request() req: RequestWithUser,
|
||||
@Path() branchId: string,
|
||||
@Body() body: BranchContactCreate,
|
||||
) {
|
||||
if (!(await prisma.branch.findFirst({ where: { id: branchId } }))) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Branch not found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
const record = await prisma.branchContact.create({
|
||||
data: { ...body, branchId, createdBy: req.user.name, updateBy: req.user.name },
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Put("{contactId}")
|
||||
async editBranchContact(
|
||||
@Request() req: RequestWithUser,
|
||||
@Body() body: BranchContactUpdate,
|
||||
@Path() branchId: string,
|
||||
@Path() contactId: string,
|
||||
) {
|
||||
if (
|
||||
!(await prisma.branchContact.findUnique({
|
||||
where: { id: contactId, branchId },
|
||||
}))
|
||||
) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Branch contact cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
|
||||
const record = await prisma.branchContact.update({
|
||||
data: { ...body, updateBy: req.user.name },
|
||||
where: { id: contactId, branchId },
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Delete("{contactId}")
|
||||
async deleteBranchContact(@Path() branchId: string, @Path() contactId: string) {
|
||||
const result = await prisma.branchContact.deleteMany({ where: { id: contactId, branchId } });
|
||||
if (result.count <= 0) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Branch contact cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
447
src/controllers/branch-controller.ts
Normal file
447
src/controllers/branch-controller.ts
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
import { Prisma, Status, UserType } from "@prisma/client";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Put,
|
||||
Path,
|
||||
Post,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
|
||||
import prisma from "../db";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
import minio from "../services/minio";
|
||||
|
||||
if (!process.env.MINIO_BUCKET) {
|
||||
throw Error("Require MinIO bucket.");
|
||||
}
|
||||
|
||||
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||
|
||||
type BranchCreate = {
|
||||
status?: Status;
|
||||
taxNo: string;
|
||||
nameEN: string;
|
||||
name: string;
|
||||
addressEN: string;
|
||||
address: string;
|
||||
zipCode: string;
|
||||
email: string;
|
||||
contactName?: string | null;
|
||||
contact?: string | string[] | null;
|
||||
telephoneNo: string;
|
||||
lineId?: string | null;
|
||||
longitude: string;
|
||||
latitude: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
headOfficeId?: string | null;
|
||||
};
|
||||
|
||||
type BranchUpdate = {
|
||||
status?: "ACTIVE" | "INACTIVE";
|
||||
taxNo?: string;
|
||||
nameEN?: string;
|
||||
name?: string;
|
||||
addressEN?: string;
|
||||
address?: string;
|
||||
zipCode?: string;
|
||||
email?: string;
|
||||
telephoneNo: string;
|
||||
contactName?: string;
|
||||
contact?: string | string[] | null;
|
||||
lineId?: string;
|
||||
longitude?: string;
|
||||
latitude?: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
headOfficeId?: string | null;
|
||||
};
|
||||
|
||||
function lineImageLoc(id: string) {
|
||||
return `branch/line-qr-${id}`;
|
||||
}
|
||||
|
||||
function branchImageLoc(id: string) {
|
||||
return `branch/branch-img-${id}`;
|
||||
}
|
||||
|
||||
@Route("api/branch")
|
||||
@Tags("Branch")
|
||||
@Security("keycloak")
|
||||
export class BranchController extends Controller {
|
||||
@Get("stats")
|
||||
async getStats() {
|
||||
const list = await prisma.branch.groupBy({
|
||||
_count: true,
|
||||
by: "isHeadOffice",
|
||||
});
|
||||
|
||||
return list.reduce<Record<"hq" | "br", number>>(
|
||||
(a, c) => {
|
||||
a[c.isHeadOffice ? "hq" : "br"] = c._count;
|
||||
return a;
|
||||
},
|
||||
{ hq: 0, br: 0 },
|
||||
);
|
||||
}
|
||||
|
||||
@Get("user-stats")
|
||||
async getUserStat(@Query() userType?: UserType) {
|
||||
const list = await prisma.branchUser.groupBy({
|
||||
_count: true,
|
||||
where: { user: { userType } },
|
||||
by: "branchId",
|
||||
});
|
||||
|
||||
const record = await prisma.branch.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
nameEN: true,
|
||||
name: true,
|
||||
isHeadOffice: true,
|
||||
},
|
||||
});
|
||||
|
||||
return record.map((a) =>
|
||||
Object.assign(a, {
|
||||
count: list.find((b) => b.branchId === a.id)?._count ?? 0,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@Get()
|
||||
async getBranch(
|
||||
@Query() zipCode?: string,
|
||||
@Query() filter?: "head" | "sub",
|
||||
@Query() headOfficeId?: string,
|
||||
@Query() tree?: boolean,
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
AND: {
|
||||
headOfficeId: headOfficeId ?? (filter === "head" || tree ? null : undefined),
|
||||
NOT: { headOfficeId: filter === "sub" && !headOfficeId ? null : undefined },
|
||||
},
|
||||
OR: [
|
||||
{ nameEN: { contains: query }, zipCode },
|
||||
{ name: { contains: query }, zipCode },
|
||||
{ email: { contains: query }, zipCode },
|
||||
{ telephoneNo: { contains: query }, zipCode },
|
||||
],
|
||||
} satisfies Prisma.BranchWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.branch.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
contact: true,
|
||||
branch: tree && {
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.branch.count({ where }),
|
||||
]);
|
||||
|
||||
return { result, page, pageSize, total };
|
||||
}
|
||||
|
||||
@Get("{branchId}")
|
||||
async getBranchById(
|
||||
@Path() branchId: string,
|
||||
@Query() includeSubBranch?: boolean,
|
||||
@Query() includeContact?: boolean,
|
||||
) {
|
||||
const record = await prisma.branch.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
branch: includeSubBranch && {
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
},
|
||||
contact: includeContact,
|
||||
},
|
||||
where: { id: branchId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
return Object.assign(record, {
|
||||
imageUrl: await minio.presignedGetObject(MINIO_BUCKET, branchImageLoc(record.id)),
|
||||
qrCodeImageUrl: await minio.presignedGetObject(MINIO_BUCKET, lineImageLoc(record.id)),
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createBranch(@Request() req: RequestWithUser, @Body() body: BranchCreate) {
|
||||
const [province, district, subDistrict, head] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||
prisma.branch.findFirst({ where: { id: body.headOfficeId || undefined } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.districtId && !district)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.subDistrictId && !subDistrict)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.headOfficeId && !head)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Head branch cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
|
||||
const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body;
|
||||
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
const last = await prisma.branch.findFirst({
|
||||
orderBy: { createdAt: "desc" },
|
||||
where: { headOfficeId: headOfficeId ?? null },
|
||||
});
|
||||
|
||||
const code = !headOfficeId
|
||||
? `HQ${year.toString().slice(2)}${+(last?.code.slice(-1) || 0) + 1}`
|
||||
: `BR${head?.code.slice(2, 5)}${(+(last?.code.slice(-2) || 0) + 1).toString().padStart(2, "0")}`;
|
||||
|
||||
const record = await prisma.branch.create({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
data: {
|
||||
...rest,
|
||||
code,
|
||||
isHeadOffice: !headOfficeId,
|
||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||
district: { connect: districtId ? { id: districtId } : undefined },
|
||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||
headOffice: { connect: headOfficeId ? { id: headOfficeId } : undefined },
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
if (headOfficeId) {
|
||||
await prisma.branch.updateMany({
|
||||
where: { id: headOfficeId, status: Status.CREATED },
|
||||
data: { status: Status.ACTIVE },
|
||||
});
|
||||
}
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
if (record && contact) {
|
||||
await prisma.branchContact.createMany({
|
||||
data:
|
||||
typeof contact === "string"
|
||||
? [{ telephoneNo: contact, branchId: record.id }]
|
||||
: contact.map((v) => ({ telephoneNo: v, branchId: record.id })),
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign(record, {
|
||||
contact: await prisma.branchContact.findMany({ where: { branchId: record.id } }),
|
||||
imageUrl: await minio.presignedGetObject(MINIO_BUCKET, branchImageLoc(record.id)),
|
||||
imageUploadUrl: await minio.presignedPutObject(MINIO_BUCKET, branchImageLoc(record.id)),
|
||||
qrCodeImageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
lineImageLoc(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
qrCodeImageUploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
lineImageLoc(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Put("{branchId}")
|
||||
async editBranch(
|
||||
@Request() req: RequestWithUser,
|
||||
@Body() body: BranchUpdate,
|
||||
@Path() branchId: string,
|
||||
) {
|
||||
if (body.headOfficeId === branchId)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Cannot make this as head office and branch at the same time.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
|
||||
if (body.subDistrictId || body.districtId || body.provinceId || body.headOfficeId) {
|
||||
const [province, district, subDistrict, branch] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||
prisma.branch.findFirst({ where: { id: body.headOfficeId || undefined } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.districtId && !district)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.subDistrictId && !subDistrict)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.headOfficeId && !branch)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Head branch cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body;
|
||||
|
||||
if (!(await prisma.branch.findUnique({ where: { id: branchId } }))) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
const record = await prisma.branch.update({
|
||||
include: { province: true, district: true, subDistrict: true },
|
||||
data: {
|
||||
...rest,
|
||||
isHeadOffice: headOfficeId !== undefined ? headOfficeId === null : undefined,
|
||||
province: {
|
||||
connect: provinceId ? { id: provinceId } : undefined,
|
||||
disconnect: provinceId === null || undefined,
|
||||
},
|
||||
district: {
|
||||
connect: districtId ? { id: districtId } : undefined,
|
||||
disconnect: districtId === null || undefined,
|
||||
},
|
||||
subDistrict: {
|
||||
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
||||
disconnect: subDistrictId === null || undefined,
|
||||
},
|
||||
headOffice: {
|
||||
connect: headOfficeId ? { id: headOfficeId } : undefined,
|
||||
disconnect: headOfficeId === null || undefined,
|
||||
},
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
where: { id: branchId },
|
||||
});
|
||||
|
||||
if (record && contact !== undefined) {
|
||||
await prisma.branchContact.deleteMany({ where: { branchId } });
|
||||
contact &&
|
||||
(await prisma.branchContact.createMany({
|
||||
data:
|
||||
typeof contact === "string"
|
||||
? [{ telephoneNo: contact, branchId }]
|
||||
: contact.map((v) => ({ telephoneNo: v, branchId })),
|
||||
}));
|
||||
}
|
||||
|
||||
return Object.assign(record, {
|
||||
imageUrl: await minio.presignedGetObject(MINIO_BUCKET, branchImageLoc(record.id)),
|
||||
imageUploadUrl: await minio.presignedPutObject(MINIO_BUCKET, branchImageLoc(record.id)),
|
||||
qrCodeImageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
lineImageLoc(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
qrCodeImageUploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
lineImageLoc(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Delete("{branchId}")
|
||||
async deleteBranch(@Path() branchId: string) {
|
||||
const record = await prisma.branch.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: branchId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
if (record.status !== Status.CREATED) {
|
||||
throw new HttpError(HttpStatus.FORBIDDEN, "Branch is in used.", "data_in_used");
|
||||
}
|
||||
|
||||
await minio.removeObject(MINIO_BUCKET, lineImageLoc(branchId), {
|
||||
forceDelete: true,
|
||||
});
|
||||
await minio.removeObject(MINIO_BUCKET, branchImageLoc(branchId), {
|
||||
forceDelete: true,
|
||||
});
|
||||
|
||||
return await prisma.branch.delete({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: branchId },
|
||||
});
|
||||
}
|
||||
}
|
||||
290
src/controllers/branch-user-controller.ts
Normal file
290
src/controllers/branch-user-controller.ts
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
import { Prisma, Status, UserType } from "@prisma/client";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Path,
|
||||
Post,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
|
||||
import prisma from "../db";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
|
||||
type BranchUserBody = { user: string[] };
|
||||
|
||||
@Route("api/branch/{branchId}/user")
|
||||
@Tags("Branch User")
|
||||
@Security("keycloak")
|
||||
export class BranchUserController extends Controller {
|
||||
@Get()
|
||||
async getBranchUser(
|
||||
@Path() branchId: string,
|
||||
@Query() zipCode?: string,
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
OR: [
|
||||
{ user: { firstName: { contains: query }, zipCode }, branchId },
|
||||
{ user: { firstNameEN: { contains: query }, zipCode }, branchId },
|
||||
{ user: { lastName: { contains: query }, zipCode }, branchId },
|
||||
{ user: { lastNameEN: { contains: query }, zipCode }, branchId },
|
||||
{ user: { email: { contains: query }, zipCode }, branchId },
|
||||
{ user: { telephoneNo: { contains: query }, zipCode }, branchId },
|
||||
],
|
||||
} satisfies Prisma.BranchUserWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.branchUser.findMany({
|
||||
orderBy: { user: { createdAt: "asc" } },
|
||||
include: {
|
||||
user: {
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.branchUser.count({ where }),
|
||||
]);
|
||||
|
||||
return { result: result.map((v) => v.user), page, pageSize, total };
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createBranchUser(
|
||||
@Request() req: RequestWithUser,
|
||||
@Path() branchId: string,
|
||||
@Body() body: BranchUserBody,
|
||||
) {
|
||||
const [branch, user] = await prisma.$transaction([
|
||||
prisma.branch.findUnique({
|
||||
where: { id: branchId },
|
||||
}),
|
||||
prisma.user.findMany({
|
||||
include: { branch: true },
|
||||
where: { id: { in: body.user } },
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!branch) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Branch cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
if (user.length !== body.user.length) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"One or more user cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.$transaction([
|
||||
prisma.user.updateMany({
|
||||
where: { id: { in: body.user }, status: Status.CREATED },
|
||||
data: { status: Status.ACTIVE },
|
||||
}),
|
||||
prisma.branchUser.createMany({
|
||||
data: user
|
||||
.filter((a) => !a.branch.some((b) => b.branchId === branchId))
|
||||
.map((v) => ({
|
||||
branchId,
|
||||
userId: v.id,
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
})),
|
||||
}),
|
||||
]);
|
||||
|
||||
const group: Record<UserType, string[]> = {
|
||||
USER: [],
|
||||
AGENCY: [],
|
||||
DELEGATE: [],
|
||||
MESSENGER: [],
|
||||
};
|
||||
|
||||
for (const u of user) group[u.userType].push(u.id);
|
||||
|
||||
for (const g of Object.values(UserType)) {
|
||||
if (group[g].length === 0) continue;
|
||||
|
||||
const last = await prisma.branchUser.findFirst({
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: { user: true },
|
||||
where: {
|
||||
branchId,
|
||||
user: {
|
||||
userType: g,
|
||||
code: { startsWith: `${branch.code.slice(4).padEnd(3, "0")}` },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const code = (idx: number) =>
|
||||
`${branch.code.slice(4).padEnd(3, "0")}${g !== "USER" ? g.charAt(0) : ""}${(+(last?.user.code?.slice(-4) || 0) + idx + 1).toString().padStart(4, "0")}`;
|
||||
|
||||
await prisma.$transaction(
|
||||
group[g].map((v, i) =>
|
||||
prisma.user.updateMany({
|
||||
where: { id: v, code: null },
|
||||
data: { code: code(i) },
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete()
|
||||
async deleteBranchUser(@Path() branchId: string, @Body() body: BranchUserBody) {
|
||||
await prisma.$transaction(
|
||||
body.user.map((v) => prisma.branchUser.deleteMany({ where: { branchId, userId: v } })),
|
||||
);
|
||||
await prisma.user.updateMany({
|
||||
where: {
|
||||
branch: { none: {} },
|
||||
},
|
||||
data: { code: null },
|
||||
});
|
||||
}
|
||||
|
||||
@Delete("{userId}")
|
||||
async deleteBranchUserById(@Path() branchId: string, @Path() userId: string) {
|
||||
await prisma.branchUser.deleteMany({
|
||||
where: { branchId, userId },
|
||||
});
|
||||
await prisma.user.updateMany({
|
||||
where: {
|
||||
id: userId,
|
||||
branch: { none: {} },
|
||||
},
|
||||
data: { code: null },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type UserBranchBody = { branch: string[] };
|
||||
|
||||
@Route("api/user/{userId}/branch")
|
||||
@Tags("Branch User")
|
||||
@Security("keycloak")
|
||||
export class UserBranchController extends Controller {
|
||||
@Get()
|
||||
async getUserBranch(
|
||||
@Path() userId: string,
|
||||
@Query() zipCode?: string,
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
OR: [
|
||||
{ branch: { name: { contains: query }, zipCode }, userId },
|
||||
{ branch: { nameEN: { contains: query }, zipCode }, userId },
|
||||
],
|
||||
} satisfies Prisma.BranchUserWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.branchUser.findMany({
|
||||
orderBy: { branch: { createdAt: "asc" } },
|
||||
include: {
|
||||
branch: {
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.branchUser.count({ where }),
|
||||
]);
|
||||
|
||||
return { result: result.map((v) => v.branch), page, pageSize, total };
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createUserBranch(
|
||||
@Request() req: RequestWithUser,
|
||||
@Path() userId: string,
|
||||
@Body() body: UserBranchBody,
|
||||
) {
|
||||
const branch = await prisma.branch.findMany({
|
||||
include: { user: true },
|
||||
where: { id: { in: body.branch } },
|
||||
});
|
||||
|
||||
if (branch.length !== body.branch.length) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"One or more branch cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.branch.updateMany({
|
||||
where: { id: { in: body.branch }, status: Status.CREATED },
|
||||
data: { status: Status.ACTIVE },
|
||||
});
|
||||
|
||||
await prisma.branchUser.createMany({
|
||||
data: branch
|
||||
.filter((a) => !a.user.some((b) => b.userId === userId))
|
||||
.map((v) => ({
|
||||
branchId: v.id,
|
||||
userId,
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
})),
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
async deleteUserBranch(@Path() userId: string, @Body() body: UserBranchBody) {
|
||||
await prisma.$transaction(
|
||||
body.branch.map((v) => prisma.branchUser.deleteMany({ where: { userId, branchId: v } })),
|
||||
);
|
||||
await prisma.user.updateMany({
|
||||
where: {
|
||||
branch: { none: {} },
|
||||
},
|
||||
data: { code: null },
|
||||
});
|
||||
}
|
||||
|
||||
@Delete("{branchId}")
|
||||
async deleteUserBranchById(@Path() branchId: string, @Path() userId: string) {
|
||||
await prisma.branchUser.deleteMany({
|
||||
where: { branchId, userId },
|
||||
});
|
||||
await prisma.user.updateMany({
|
||||
where: {
|
||||
id: userId,
|
||||
branch: { none: {} },
|
||||
},
|
||||
data: { code: null },
|
||||
});
|
||||
}
|
||||
}
|
||||
356
src/controllers/customer-branch-controller.ts
Normal file
356
src/controllers/customer-branch-controller.ts
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
import { Prisma, Status } from "@prisma/client";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Path,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
import prisma from "../db";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import minio from "../services/minio";
|
||||
|
||||
if (!process.env.MINIO_BUCKET) {
|
||||
throw Error("Require MinIO bucket.");
|
||||
}
|
||||
|
||||
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||
|
||||
function imageLocation(id: string) {
|
||||
return `employee/profile-img-${id}`;
|
||||
}
|
||||
|
||||
type CustomerBranchCreate = {
|
||||
customerId: string;
|
||||
|
||||
status?: Status;
|
||||
|
||||
legalPersonNo: string;
|
||||
|
||||
taxNo: string;
|
||||
name: string;
|
||||
nameEN: string;
|
||||
addressEN: string;
|
||||
address: string;
|
||||
zipCode: string;
|
||||
email: string;
|
||||
telephoneNo: string;
|
||||
longitude: string;
|
||||
latitude: string;
|
||||
|
||||
registerName: string;
|
||||
registerDate: Date;
|
||||
authorizedCapital: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
};
|
||||
|
||||
type CustomerBranchUpdate = {
|
||||
customerId?: string;
|
||||
|
||||
status?: "ACTIVE" | "INACTIVE";
|
||||
|
||||
legalPersonNo?: string;
|
||||
|
||||
taxNo?: string;
|
||||
name?: string;
|
||||
nameEN?: string;
|
||||
addressEN?: string;
|
||||
address?: string;
|
||||
zipCode?: string;
|
||||
email?: string;
|
||||
telephoneNo?: string;
|
||||
longitude?: string;
|
||||
latitude?: string;
|
||||
|
||||
registerName?: string;
|
||||
registerDate?: Date;
|
||||
authorizedCapital?: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
};
|
||||
|
||||
@Route("api/customer-branch")
|
||||
@Tags("Customer Branch")
|
||||
@Security("keycloak")
|
||||
export class CustomerBranchController extends Controller {
|
||||
@Get()
|
||||
async list(
|
||||
@Query() zipCode?: string,
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
OR: [
|
||||
{ nameEN: { contains: query }, zipCode },
|
||||
{ name: { contains: query }, zipCode },
|
||||
{ email: { contains: query }, zipCode },
|
||||
],
|
||||
} satisfies Prisma.BranchWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.customerBranch.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.customerBranch.count({ where }),
|
||||
]);
|
||||
|
||||
return { result, page, pageSize, total };
|
||||
}
|
||||
|
||||
@Get("{branchId}")
|
||||
async getById(@Path() branchId: string) {
|
||||
const record = await prisma.customerBranch.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: branchId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Get("{branchId}/employee")
|
||||
async listEmployee(
|
||||
@Path() branchId: string,
|
||||
@Query() zipCode?: string,
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
AND: { customerBranchId: branchId },
|
||||
OR: [
|
||||
{ firstName: { contains: query }, zipCode },
|
||||
{ firstNameEN: { contains: query }, zipCode },
|
||||
{ lastName: { contains: query }, zipCode },
|
||||
{ lastNameEN: { contains: query }, zipCode },
|
||||
{ email: { contains: query }, zipCode },
|
||||
],
|
||||
} satisfies Prisma.EmployeeWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.employee.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.employee.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
result: await Promise.all(
|
||||
result.map(async (v) => ({
|
||||
...v,
|
||||
profileImageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(v.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
})),
|
||||
),
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Request() req: RequestWithUser, @Body() body: CustomerBranchCreate) {
|
||||
if (body.provinceId || body.districtId || body.subDistrictId || body.customerId) {
|
||||
const [province, district, subDistrict, customer] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||
prisma.customer.findFirst({ where: { id: body.customerId || undefined } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.districtId && !district)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.subDistrictId && !subDistrict)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.customerId && !customer)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Customer cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const { provinceId, districtId, subDistrictId, customerId, ...rest } = body;
|
||||
|
||||
const count = await prisma.customerBranch.count({
|
||||
where: { customerId },
|
||||
});
|
||||
|
||||
const record = await prisma.customerBranch.create({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
data: {
|
||||
...rest,
|
||||
branchNo: `${count + 1}`,
|
||||
customer: { connect: { id: customerId } },
|
||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||
district: { connect: districtId ? { id: districtId } : undefined },
|
||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.customer.updateMany({
|
||||
where: { id: customerId, status: Status.CREATED },
|
||||
data: { status: Status.ACTIVE },
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Put("{branchId}")
|
||||
async editById(
|
||||
@Request() req: RequestWithUser,
|
||||
@Body() body: CustomerBranchUpdate,
|
||||
@Path() branchId: string,
|
||||
) {
|
||||
if (body.provinceId || body.districtId || body.subDistrictId || body.customerId) {
|
||||
const [province, district, subDistrict, customer] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||
prisma.customer.findFirst({ where: { id: body.customerId || undefined } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.districtId && !district)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.subDistrictId && !subDistrict)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.customerId && !customer)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Customer cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const { provinceId, districtId, subDistrictId, customerId, ...rest } = body;
|
||||
|
||||
if (!(await prisma.customerBranch.findUnique({ where: { id: branchId } }))) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
const record = await prisma.customerBranch.update({
|
||||
where: { id: branchId },
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
data: {
|
||||
...rest,
|
||||
customer: { connect: customerId ? { id: customerId } : undefined },
|
||||
province: {
|
||||
connect: provinceId ? { id: provinceId } : undefined,
|
||||
disconnect: provinceId === null || undefined,
|
||||
},
|
||||
district: {
|
||||
connect: districtId ? { id: districtId } : undefined,
|
||||
disconnect: districtId === null || undefined,
|
||||
},
|
||||
subDistrict: {
|
||||
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
||||
disconnect: subDistrictId === null || undefined,
|
||||
},
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Delete("{branchId}")
|
||||
async delete(@Path() branchId: string) {
|
||||
const record = await prisma.customerBranch.findFirst({ where: { id: branchId } });
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Customer branch cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
|
||||
if (record.status !== Status.CREATED) {
|
||||
throw new HttpError(HttpStatus.FORBIDDEN, "Customer branch is in used.", "data_in_used");
|
||||
}
|
||||
|
||||
return await prisma.customerBranch.delete({ where: { id: branchId } });
|
||||
}
|
||||
}
|
||||
178
src/controllers/customer-controller.ts
Normal file
178
src/controllers/customer-controller.ts
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import { CustomerType, Prisma, Status } from "@prisma/client";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Path,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
import prisma from "../db";
|
||||
import minio from "../services/minio";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
|
||||
if (!process.env.MINIO_BUCKET) {
|
||||
throw Error("Require MinIO bucket.");
|
||||
}
|
||||
|
||||
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||
|
||||
export type CustomerCreate = {
|
||||
status?: Status;
|
||||
customerType: CustomerType;
|
||||
customerName: string;
|
||||
customerNameEN: string;
|
||||
};
|
||||
|
||||
export type CustomerUpdate = {
|
||||
status?: "ACTIVE" | "INACTIVE";
|
||||
customerType?: CustomerType;
|
||||
customerName?: string;
|
||||
customerNameEN?: string;
|
||||
};
|
||||
|
||||
function imageLocation(id: string) {
|
||||
return `customer/img-${id}`;
|
||||
}
|
||||
|
||||
@Route("api/customer")
|
||||
@Tags("Customer")
|
||||
@Security("keycloak")
|
||||
export class CustomerController extends Controller {
|
||||
@Get()
|
||||
async list(
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
OR: [{ customerName: { contains: query } }, { customerNameEN: { contains: query } }],
|
||||
} satisfies Prisma.CustomerWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.customer.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.customer.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
result: await Promise.all(
|
||||
result.map(async (v) => ({
|
||||
...v,
|
||||
imageUrl: await minio.presignedGetObject(MINIO_BUCKET, imageLocation(v.id), 12 * 60 * 60),
|
||||
})),
|
||||
),
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("{customerId}")
|
||||
async getById(@Path() customerId: string) {
|
||||
const record = await prisma.customer.findFirst({ where: { id: customerId } });
|
||||
if (!record)
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found");
|
||||
return Object.assign(record, {
|
||||
imageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) {
|
||||
const last = await prisma.customer.findFirst({
|
||||
orderBy: { createdAt: "desc" },
|
||||
where: { customerType: body.customerType },
|
||||
});
|
||||
|
||||
const code = `${body.customerType}${(+(last?.code.slice(-6) || 0) + 1).toString().padStart(6, "0")}`;
|
||||
|
||||
const record = await prisma.customer.create({
|
||||
data: {
|
||||
...body,
|
||||
code,
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return Object.assign(record, {
|
||||
imageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
imageUploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Put("{customerId}")
|
||||
async editById(
|
||||
@Path() customerId: string,
|
||||
@Request() req: RequestWithUser,
|
||||
@Body() body: CustomerUpdate,
|
||||
) {
|
||||
if (!(await prisma.customer.findUnique({ where: { id: customerId } }))) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
const record = await prisma.customer.update({
|
||||
where: { id: customerId },
|
||||
data: {
|
||||
...body,
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
return Object.assign(record, {
|
||||
imageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
imageUploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Delete("{customerId}")
|
||||
async deleteById(@Path() customerId: string) {
|
||||
const record = await prisma.customer.findFirst({ where: { id: customerId } });
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
if (record.status !== Status.CREATED) {
|
||||
throw new HttpError(HttpStatus.FORBIDDEN, "Customer is in used.", "data_in_used");
|
||||
}
|
||||
|
||||
return await prisma.customer.delete({ where: { id: customerId } });
|
||||
}
|
||||
}
|
||||
183
src/controllers/employee-checkup-controller.ts
Normal file
183
src/controllers/employee-checkup-controller.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Path,
|
||||
Post,
|
||||
Put,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
import prisma from "../db";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
|
||||
type EmployeeCheckupCreate = {
|
||||
checkupType: string;
|
||||
checkupResult: string;
|
||||
|
||||
provinceId?: string | null;
|
||||
|
||||
hospitalName: string;
|
||||
remark: string;
|
||||
medicalBenefitScheme: string;
|
||||
insuranceCompany: string;
|
||||
coverageStartDate: Date;
|
||||
coverageExpireDate: Date;
|
||||
};
|
||||
|
||||
type EmployeeCheckupEdit = {
|
||||
checkupType?: string;
|
||||
checkupResult?: string;
|
||||
|
||||
provinceId?: string | null;
|
||||
|
||||
hospitalName?: string;
|
||||
remark?: string;
|
||||
medicalBenefitScheme?: string;
|
||||
insuranceCompany?: string;
|
||||
coverageStartDate?: Date;
|
||||
coverageExpireDate?: Date;
|
||||
};
|
||||
|
||||
@Route("api/employee/{employeeId}/checkup")
|
||||
@Tags("Employee Checkup")
|
||||
@Security("keycloak")
|
||||
export class EmployeeCheckupController extends Controller {
|
||||
@Get()
|
||||
async list(@Path() employeeId: string) {
|
||||
return prisma.employeeCheckup.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
where: { employeeId },
|
||||
});
|
||||
}
|
||||
|
||||
@Get("{checkupId}")
|
||||
async getById(@Path() employeeId: string, @Path() checkupId: string) {
|
||||
const record = await prisma.employeeCheckup.findFirst({
|
||||
where: { id: checkupId, employeeId },
|
||||
});
|
||||
if (!record) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Employee checkup cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Request() req: RequestWithUser,
|
||||
@Path() employeeId: string,
|
||||
@Body() body: EmployeeCheckupCreate,
|
||||
) {
|
||||
if (body.provinceId || employeeId) {
|
||||
const [province, employee] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.employee.findFirst({ where: { id: employeeId } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (!employee)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Employee cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const { provinceId, ...rest } = body;
|
||||
|
||||
const record = await prisma.employeeCheckup.create({
|
||||
include: { province: true },
|
||||
data: {
|
||||
...rest,
|
||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||
employee: { connect: { id: employeeId } },
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Put("{checkupId}")
|
||||
async editById(
|
||||
@Request() req: RequestWithUser,
|
||||
@Path() employeeId: string,
|
||||
@Path() checkupId: string,
|
||||
@Body() body: EmployeeCheckupEdit,
|
||||
) {
|
||||
if (body.provinceId || employeeId) {
|
||||
const [province, employee] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.employee.findFirst({ where: { id: employeeId } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (!employee)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Employee cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const { provinceId, ...rest } = body;
|
||||
|
||||
if (!(await prisma.employeeCheckup.findUnique({ where: { id: checkupId, employeeId } }))) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Employee checkup cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
|
||||
const record = await prisma.employeeCheckup.update({
|
||||
include: { province: true },
|
||||
where: { id: checkupId, employeeId },
|
||||
data: {
|
||||
...rest,
|
||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Delete("{checkupId}")
|
||||
async deleteById(@Path() employeeId: string, @Path() checkupId: string) {
|
||||
const record = await prisma.employeeCheckup.findFirst({ where: { id: checkupId, employeeId } });
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Employee checkup cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
|
||||
return await prisma.employeeCheckup.delete({ where: { id: checkupId, employeeId } });
|
||||
}
|
||||
}
|
||||
330
src/controllers/employee-controller.ts
Normal file
330
src/controllers/employee-controller.ts
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
import { Prisma, Status } from "@prisma/client";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Path,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
import prisma from "../db";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import minio from "../services/minio";
|
||||
|
||||
if (!process.env.MINIO_BUCKET) {
|
||||
throw Error("Require MinIO bucket.");
|
||||
}
|
||||
|
||||
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||
|
||||
function imageLocation(id: string) {
|
||||
return `employee/profile-img-${id}`;
|
||||
}
|
||||
|
||||
type EmployeeCreate = {
|
||||
customerBranchId: string;
|
||||
|
||||
status?: Status;
|
||||
|
||||
code: string;
|
||||
nrcNo: string;
|
||||
|
||||
dateOfBirth: Date;
|
||||
gender: string;
|
||||
nationality: string;
|
||||
|
||||
firstName: string;
|
||||
firstNameEN: string;
|
||||
lastName: string;
|
||||
lastNameEN: string;
|
||||
|
||||
addressEN: string;
|
||||
address: string;
|
||||
zipCode: string;
|
||||
email: string;
|
||||
telephoneNo: string;
|
||||
|
||||
arrivalBarricade: string;
|
||||
arrivalCardNo: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
};
|
||||
|
||||
type EmployeeUpdate = {
|
||||
customerBranchId?: string;
|
||||
status?: "ACTIVE" | "INACTIVE";
|
||||
|
||||
code?: string;
|
||||
nrcNo?: string;
|
||||
|
||||
dateOfBirth?: Date;
|
||||
gender?: string;
|
||||
nationality?: string;
|
||||
|
||||
firstName?: string;
|
||||
firstNameEN?: string;
|
||||
lastName?: string;
|
||||
lastNameEN?: string;
|
||||
|
||||
addressEN?: string;
|
||||
address?: string;
|
||||
zipCode?: string;
|
||||
email?: string;
|
||||
telephoneNo?: string;
|
||||
|
||||
arrivalBarricade?: string;
|
||||
arrivalCardNo?: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
};
|
||||
|
||||
@Route("api/employee")
|
||||
@Tags("Employee")
|
||||
@Security("keycloak")
|
||||
export class EmployeeController extends Controller {
|
||||
@Get()
|
||||
async list(
|
||||
@Query() zipCode?: string,
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
OR: [
|
||||
{ firstName: { contains: query }, zipCode },
|
||||
{ firstNameEN: { contains: query }, zipCode },
|
||||
{ lastName: { contains: query }, zipCode },
|
||||
{ lastNameEN: { contains: query }, zipCode },
|
||||
{ email: { contains: query }, zipCode },
|
||||
],
|
||||
} satisfies Prisma.EmployeeWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.employee.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.employee.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
result: await Promise.all(
|
||||
result.map(async (v) => ({
|
||||
...v,
|
||||
profileImageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(v.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
})),
|
||||
),
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("{employeeId}")
|
||||
async getById(@Path() employeeId: string) {
|
||||
const record = await prisma.employee.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: employeeId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Request() req: RequestWithUser, @Body() body: EmployeeCreate) {
|
||||
if (body.provinceId || body.districtId || body.subDistrictId || body.customerBranchId) {
|
||||
const [province, district, subDistrict, customerBranch] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||
prisma.customerBranch.findFirst({ where: { id: body.customerBranchId || undefined } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.districtId && !district)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.subDistrictId && !subDistrict)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.customerBranchId && !customerBranch)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Customer Branch cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body;
|
||||
|
||||
const record = await prisma.employee.create({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
data: {
|
||||
...rest,
|
||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||
district: { connect: districtId ? { id: districtId } : undefined },
|
||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||
customerBranch: { connect: { id: customerBranchId } },
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.customerBranch.updateMany({
|
||||
where: { id: customerBranchId, status: Status.CREATED },
|
||||
data: { status: Status.ACTIVE },
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return Object.assign(record, {
|
||||
profileImageUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
profileImageUploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Put("{employeeId}")
|
||||
async editById(
|
||||
@Request() req: RequestWithUser,
|
||||
@Body() body: EmployeeUpdate,
|
||||
@Path() employeeId: string,
|
||||
) {
|
||||
if (body.provinceId || body.districtId || body.subDistrictId || body.customerBranchId) {
|
||||
const [province, district, subDistrict, customerBranch] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||
prisma.customerBranch.findFirst({ where: { id: body.customerBranchId || undefined } }),
|
||||
]);
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.districtId && !district)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.subDistrictId && !subDistrict)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.customerBranchId && !customerBranch)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Customer cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body;
|
||||
|
||||
const record = await prisma.employee.update({
|
||||
where: { id: employeeId },
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
data: {
|
||||
...rest,
|
||||
customerBranch: { connect: customerBranchId ? { id: customerBranchId } : undefined },
|
||||
province: {
|
||||
connect: provinceId ? { id: provinceId } : undefined,
|
||||
disconnect: provinceId === null || undefined,
|
||||
},
|
||||
district: {
|
||||
connect: districtId ? { id: districtId } : undefined,
|
||||
disconnect: districtId === null || undefined,
|
||||
},
|
||||
subDistrict: {
|
||||
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
||||
disconnect: subDistrictId === null || undefined,
|
||||
},
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Delete("{employeeId}")
|
||||
async delete(@Path() employeeId: string) {
|
||||
const record = await prisma.employee.findFirst({ where: { id: employeeId } });
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
if (record.status !== Status.CREATED) {
|
||||
throw new HttpError(
|
||||
HttpStatus.FORBIDDEN,
|
||||
"Emplyee is in used.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
return await prisma.employee.delete({ where: { id: employeeId } });
|
||||
}
|
||||
}
|
||||
127
src/controllers/employee-other-info-controller.ts
Normal file
127
src/controllers/employee-other-info-controller.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import { Prisma, Status } from "@prisma/client";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Put,
|
||||
Path,
|
||||
Post,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
|
||||
import prisma from "../db";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
|
||||
type EmployeeOtherInfoCreate = {
|
||||
citizenId: string;
|
||||
fatherFullName: string;
|
||||
motherFullName: string;
|
||||
birthPlace: string;
|
||||
};
|
||||
|
||||
type EmployeeOtherInfoUpdate = {
|
||||
citizenId: string;
|
||||
fatherFullName: string;
|
||||
motherFullName: string;
|
||||
birthPlace: string;
|
||||
};
|
||||
|
||||
@Route("api/employee/{employeeId}/other-info")
|
||||
@Tags("Employee Other Info")
|
||||
@Security("keycloak")
|
||||
export class EmployeeOtherInfo extends Controller {
|
||||
@Get()
|
||||
async list(@Path() employeeId: string) {
|
||||
return prisma.employeeOtherInfo.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
where: { employeeId },
|
||||
});
|
||||
}
|
||||
|
||||
@Get("{otherInfoId}")
|
||||
async getById(@Path() employeeId: string, @Path() otherInfoId: string) {
|
||||
const record = await prisma.employeeOtherInfo.findFirst({
|
||||
where: { id: otherInfoId, employeeId },
|
||||
});
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Employee info cannot be found.", "data_not_found");
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Request() req: RequestWithUser,
|
||||
@Path() employeeId: string,
|
||||
@Body() body: EmployeeOtherInfoCreate,
|
||||
) {
|
||||
if (!(await prisma.employee.findUnique({ where: { id: employeeId } })))
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Employee cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
|
||||
const record = await prisma.employeeOtherInfo.create({
|
||||
data: {
|
||||
...body,
|
||||
employee: { connect: { id: employeeId } },
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Put("{otherInfoId}")
|
||||
async editById(
|
||||
@Request() req: RequestWithUser,
|
||||
@Path() employeeId: string,
|
||||
@Path() otherInfoId: string,
|
||||
@Body() body: EmployeeOtherInfoUpdate,
|
||||
) {
|
||||
if (!(await prisma.employeeOtherInfo.findUnique({ where: { id: otherInfoId, employeeId } }))) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Employee other info cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
|
||||
const record = await prisma.employeeOtherInfo.update({
|
||||
where: { id: otherInfoId, employeeId },
|
||||
data: { ...body, createdBy: req.user.name, updateBy: req.user.name },
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Delete("{otherInfoId}")
|
||||
async deleteById(@Path() employeeId: string, @Path() otherInfoId: string) {
|
||||
const record = await prisma.employeeOtherInfo.findFirst({
|
||||
where: { id: otherInfoId, employeeId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Employee other info cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
|
||||
return await prisma.employeeOtherInfo.delete({ where: { id: otherInfoId, employeeId } });
|
||||
}
|
||||
}
|
||||
76
src/controllers/keycloak-controller.ts
Normal file
76
src/controllers/keycloak-controller.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { Body, Controller, Delete, Get, Path, Post, Put, Route, Security, Tags } from "tsoa";
|
||||
import {
|
||||
addUserRoles,
|
||||
createUser,
|
||||
deleteUser,
|
||||
editUser,
|
||||
getRoles,
|
||||
removeUserRoles,
|
||||
} from "../services/keycloak";
|
||||
|
||||
@Route("api/keycloak")
|
||||
@Tags("Single-Sign On")
|
||||
@Security("keycloak")
|
||||
export class KeycloakController extends Controller {
|
||||
@Post("user")
|
||||
async createUser(
|
||||
@Body() body: { username: string; password: string; firstName?: string; lastName?: string },
|
||||
) {
|
||||
return await createUser(body.username, body.password, {
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
requiredActions: ["UPDATE_PASSWORD"],
|
||||
});
|
||||
}
|
||||
|
||||
@Put("user/{userId}")
|
||||
async editUser(
|
||||
@Path() userId: string,
|
||||
@Body() body: { username?: string; password?: string; firstName?: string; lastName?: string },
|
||||
) {
|
||||
return await editUser(userId, body);
|
||||
}
|
||||
|
||||
@Delete("user/{userId}")
|
||||
async deleteUser(@Path() userId: string) {
|
||||
return await deleteUser(userId);
|
||||
}
|
||||
|
||||
@Get("role")
|
||||
async getRole() {
|
||||
const role = await getRoles();
|
||||
if (Array.isArray(role))
|
||||
return role.filter(
|
||||
(a) =>
|
||||
!["uma_authorization", "offline_access", "default-roles"].some((b) => a.name.includes(b)),
|
||||
);
|
||||
throw new Error("Failed. Cannot get role.");
|
||||
}
|
||||
|
||||
@Post("{userId}/role")
|
||||
async addRole(@Path() userId: string, @Body() body: { role: string[] }) {
|
||||
const list = await getRoles();
|
||||
|
||||
if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server.");
|
||||
|
||||
const result = await addUserRoles(
|
||||
userId,
|
||||
list.filter((v) => body.role.includes(v.id)),
|
||||
);
|
||||
|
||||
if (!result) throw new Error("Failed. Cannot set user's role.");
|
||||
}
|
||||
|
||||
@Delete("{userId}/role/{roleId}")
|
||||
async deleteRole(@Path() userId: string, @Path() roleId: string) {
|
||||
const list = await getRoles();
|
||||
|
||||
if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server.");
|
||||
|
||||
const result = await removeUserRoles(
|
||||
userId,
|
||||
list.filter((v) => roleId === v.id),
|
||||
);
|
||||
if (!result) throw new Error("Failed. Cannot remove user's role.");
|
||||
}
|
||||
}
|
||||
178
src/controllers/permission-controller.ts
Normal file
178
src/controllers/permission-controller.ts
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import { Body, Controller, Delete, Get, Path, Post, Put, Route, Security, Tags } from "tsoa";
|
||||
import prisma from "../db";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
|
||||
type MenuCreate = {
|
||||
caption: string;
|
||||
captionEN: string;
|
||||
menuType: string;
|
||||
url: string;
|
||||
parentId?: string;
|
||||
};
|
||||
|
||||
type MenuEdit = {
|
||||
caption: string;
|
||||
captionEN: string;
|
||||
menuType: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
@Route("v1/permission/menu")
|
||||
@Tags("Permission")
|
||||
@Security("keycloak")
|
||||
export class MenuController extends Controller {
|
||||
@Get()
|
||||
async listMenu() {
|
||||
const record = await prisma.menu.findMany({
|
||||
include: { children: true, roleMenuPermission: true },
|
||||
orderBy: { createdAt: "asc" },
|
||||
});
|
||||
return record;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createMenu(@Body() body: MenuCreate) {
|
||||
if (body.parentId) {
|
||||
const parent = await prisma.menu.findFirst({ where: { id: body.parentId } });
|
||||
|
||||
if (!parent) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Parent menu not found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const record = await prisma.menu.create({
|
||||
include: { parent: true },
|
||||
data: body,
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Put("{menuId}")
|
||||
async editMenu(@Path("menuId") id: string, @Body() body: MenuEdit) {
|
||||
const record = await prisma.menu
|
||||
.update({
|
||||
include: { parent: true },
|
||||
where: { id },
|
||||
data: body,
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Menu cannot be found.", "data_not_found");
|
||||
}
|
||||
throw new Error(e);
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Delete("{menuId}")
|
||||
async deleteMenu(@Path("menuId") id: string) {
|
||||
const record = await prisma.menu.deleteMany({ where: { id } });
|
||||
if (record.count <= 0) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Menu cannot be found.", "data_not_found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type MenuComponentCreate = {
|
||||
componentId: string;
|
||||
componentTag: string;
|
||||
menuId: string;
|
||||
};
|
||||
|
||||
type MenuComponentEdit = {
|
||||
componentId?: string;
|
||||
componentTag?: string;
|
||||
menuId?: string;
|
||||
};
|
||||
|
||||
@Route("v1/permission/menu-component")
|
||||
@Tags("Permission")
|
||||
@Security("keycloak")
|
||||
export class MenuComponentController extends Controller {
|
||||
@Get()
|
||||
async listMenuComponent() {
|
||||
const record = await prisma.menuComponent.findMany({
|
||||
include: { roleMenuComponentPermission: true },
|
||||
orderBy: { createdAt: "asc" },
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createMenuComponent(@Body() body: MenuComponentCreate) {
|
||||
const menu = await prisma.menu.findFirst({ where: { id: body.menuId } });
|
||||
|
||||
if (!menu) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Menu not found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const record = await prisma.menuComponent.create({
|
||||
data: body,
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Put("{menuComponentId}")
|
||||
async editMenuComponent(@Path("menuComponentId") id: string, @Body() body: MenuComponentEdit) {
|
||||
if (body.menuId) {
|
||||
const menu = await prisma.menu.findFirst({ where: { id: body.menuId } });
|
||||
|
||||
if (!menu) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Menu not found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const record = await prisma.menuComponent
|
||||
.update({
|
||||
include: { roleMenuComponentPermission: true },
|
||||
where: { id },
|
||||
data: body,
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Menu component cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
throw new Error(e);
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Delete("{menuComponentId}")
|
||||
async deleteMenuComponent(@Path("menuComponentId") id: string) {
|
||||
const record = await prisma.menuComponent.deleteMany({ where: { id } });
|
||||
if (record.count <= 0) {
|
||||
throw new HttpError(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Menu component cannot be found.",
|
||||
"data_not_found",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
576
src/controllers/user-controller.ts
Normal file
576
src/controllers/user-controller.ts
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Put,
|
||||
Path,
|
||||
Post,
|
||||
Query,
|
||||
Request,
|
||||
Route,
|
||||
Security,
|
||||
Tags,
|
||||
} from "tsoa";
|
||||
import { Prisma, Status, UserType } from "@prisma/client";
|
||||
|
||||
import prisma from "../db";
|
||||
import minio from "../services/minio";
|
||||
import { RequestWithUser } from "../interfaces/user";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import {
|
||||
addUserRoles,
|
||||
createUser,
|
||||
deleteUser,
|
||||
editUser,
|
||||
getRoles,
|
||||
getUserRoles,
|
||||
removeUserRoles,
|
||||
} from "../services/keycloak";
|
||||
|
||||
if (!process.env.MINIO_BUCKET) {
|
||||
throw Error("Require MinIO bucket.");
|
||||
}
|
||||
|
||||
const MINIO_BUCKET = process.env.MINIO_BUCKET;
|
||||
|
||||
type UserCreate = {
|
||||
status?: Status;
|
||||
|
||||
userType: UserType;
|
||||
userRole: string;
|
||||
|
||||
username: string;
|
||||
|
||||
firstName: string;
|
||||
firstNameEN: string;
|
||||
lastName: string;
|
||||
lastNameEN: string;
|
||||
gender: string;
|
||||
|
||||
checkpoint?: string | null;
|
||||
checkpointEN?: string | null;
|
||||
registrationNo?: string | null;
|
||||
startDate?: Date | null;
|
||||
retireDate?: Date | null;
|
||||
discountCondition?: string | null;
|
||||
licenseNo?: string | null;
|
||||
licenseIssueDate?: Date | null;
|
||||
licenseExpireDate?: Date | null;
|
||||
sourceNationality?: string | null;
|
||||
importNationality?: string | null;
|
||||
trainingPlace?: string | null;
|
||||
responsibleArea?: string | null;
|
||||
birthDate?: Date | null;
|
||||
|
||||
address: string;
|
||||
addressEN: string;
|
||||
zipCode: string;
|
||||
email: string;
|
||||
telephoneNo: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
};
|
||||
|
||||
type UserUpdate = {
|
||||
status?: "ACTIVE" | "INACTIVE";
|
||||
|
||||
username?: string;
|
||||
|
||||
userType?: UserType;
|
||||
userRole?: string;
|
||||
|
||||
firstName?: string;
|
||||
firstNameEN?: string;
|
||||
lastName?: string;
|
||||
lastNameEN?: string;
|
||||
gender?: string;
|
||||
|
||||
checkpoint?: string | null;
|
||||
checkpointEN?: string | null;
|
||||
registrationNo?: string | null;
|
||||
startDate?: Date | null;
|
||||
retireDate?: Date | null;
|
||||
discountCondition?: string | null;
|
||||
licenseNo?: string | null;
|
||||
licenseIssueDate?: Date | null;
|
||||
licenseExpireDate?: Date | null;
|
||||
sourceNationality?: string | null;
|
||||
importNationality?: string | null;
|
||||
trainingPlace?: string | null;
|
||||
responsibleArea?: string | null;
|
||||
birthDate?: Date | null;
|
||||
|
||||
address?: string;
|
||||
addressEN?: string;
|
||||
zipCode?: string;
|
||||
email?: string;
|
||||
telephoneNo?: string;
|
||||
|
||||
subDistrictId?: string | null;
|
||||
districtId?: string | null;
|
||||
provinceId?: string | null;
|
||||
};
|
||||
|
||||
function imageLocation(id: string) {
|
||||
return `user/profile-img-${id}`;
|
||||
}
|
||||
|
||||
@Route("api/user")
|
||||
@Tags("User")
|
||||
@Security("keycloak")
|
||||
export class UserController extends Controller {
|
||||
@Get("type-stats")
|
||||
async getUserTypeStats() {
|
||||
const list = await prisma.user.groupBy({
|
||||
by: "userType",
|
||||
_count: true,
|
||||
});
|
||||
|
||||
return list.reduce<Record<UserType, number>>(
|
||||
(a, c) => {
|
||||
a[c.userType] = c._count;
|
||||
return a;
|
||||
},
|
||||
{
|
||||
USER: 0,
|
||||
MESSENGER: 0,
|
||||
DELEGATE: 0,
|
||||
AGENCY: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@Get()
|
||||
async getUser(
|
||||
@Query() userType?: UserType,
|
||||
@Query() zipCode?: string,
|
||||
@Query() includeBranch: boolean = false,
|
||||
@Query() query: string = "",
|
||||
@Query() page: number = 1,
|
||||
@Query() pageSize: number = 30,
|
||||
) {
|
||||
const where = {
|
||||
OR: [
|
||||
{ firstName: { contains: query }, zipCode, userType },
|
||||
{ firstNameEN: { contains: query }, zipCode, userType },
|
||||
{ lastName: { contains: query }, zipCode, userType },
|
||||
{ lastNameEN: { contains: query }, zipCode, userType },
|
||||
{ email: { contains: query }, zipCode, userType },
|
||||
{ telephoneNo: { contains: query }, zipCode, userType },
|
||||
],
|
||||
} satisfies Prisma.UserWhereInput;
|
||||
|
||||
const [result, total] = await prisma.$transaction([
|
||||
prisma.user.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
branch: { include: { branch: includeBranch } },
|
||||
},
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
}),
|
||||
prisma.user.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
result: await Promise.all(
|
||||
result.map(async (v) => ({
|
||||
...v,
|
||||
branch: includeBranch ? v.branch.map((a) => a.branch) : undefined,
|
||||
profileImageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(v.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
})),
|
||||
),
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("{userId}")
|
||||
async getUserById(@Path() userId: string) {
|
||||
const record = await prisma.user.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!record)
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
||||
|
||||
return Object.assign(record, {
|
||||
profileImageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createUser(@Request() req: RequestWithUser, @Body() body: UserCreate) {
|
||||
if (body.provinceId || body.districtId || body.subDistrictId) {
|
||||
const [province, district, subDistrict] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId ?? undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId ?? undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId ?? undefined } }),
|
||||
]);
|
||||
if (body.provinceId && !province) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
if (body.districtId && !district) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
if (body.subDistrictId && !subDistrict) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { provinceId, districtId, subDistrictId, username, ...rest } = body;
|
||||
|
||||
let list = await getRoles();
|
||||
|
||||
if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server.");
|
||||
if (Array.isArray(list)) {
|
||||
list = list.filter(
|
||||
(a) =>
|
||||
!["uma_authorization", "offline_access", "default-roles"].some((b) => a.name.includes(b)),
|
||||
);
|
||||
}
|
||||
|
||||
const userId = await createUser(username, username, {
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
requiredActions: ["UPDATE_PASSWORD"],
|
||||
});
|
||||
|
||||
if (!userId || typeof userId !== "string") {
|
||||
throw new Error("Cannot create user with keycloak service.");
|
||||
}
|
||||
|
||||
const role = list.find((v) => v.name === body.userRole);
|
||||
|
||||
const resultAddRole = role && (await addUserRoles(userId, [role]));
|
||||
|
||||
if (!resultAddRole) {
|
||||
await deleteUser(userId);
|
||||
throw new Error("Failed. Cannot set user's role.");
|
||||
}
|
||||
|
||||
const record = await prisma.user.create({
|
||||
include: { province: true, district: true, subDistrict: true },
|
||||
data: {
|
||||
id: userId,
|
||||
...rest,
|
||||
username,
|
||||
userRole: role.name,
|
||||
province: { connect: provinceId ? { id: provinceId } : undefined },
|
||||
district: { connect: districtId ? { id: districtId } : undefined },
|
||||
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
|
||||
createdBy: req.user.name,
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
this.setStatus(HttpStatus.CREATED);
|
||||
|
||||
return Object.assign(record, {
|
||||
profileImageUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
profileImageUploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Put("{userId}")
|
||||
async editUser(
|
||||
@Request() req: RequestWithUser,
|
||||
@Body() body: UserUpdate,
|
||||
@Path() userId: string,
|
||||
) {
|
||||
if (body.subDistrictId || body.districtId || body.provinceId) {
|
||||
const [province, district, subDistrict] = await prisma.$transaction([
|
||||
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
|
||||
prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
|
||||
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
|
||||
]);
|
||||
|
||||
if (body.provinceId && !province)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Province cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.districtId && !district)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"District cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
if (body.subDistrictId && !subDistrict)
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Sub-district cannot be found.",
|
||||
"missing_or_invalid_parameter",
|
||||
);
|
||||
}
|
||||
|
||||
let userRole: string | undefined;
|
||||
|
||||
if (body.userRole) {
|
||||
let list = await getRoles();
|
||||
|
||||
if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server.");
|
||||
if (Array.isArray(list)) {
|
||||
list = list.filter(
|
||||
(a) =>
|
||||
!["uma_authorization", "offline_access", "default-roles"].some((b) =>
|
||||
a.name.includes(b),
|
||||
),
|
||||
);
|
||||
}
|
||||
const currentRole = await getUserRoles(userId);
|
||||
|
||||
const role = list.find((v) => v.name === body.userRole);
|
||||
|
||||
const resultAddRole = role && (await addUserRoles(userId, [role]));
|
||||
|
||||
if (!resultAddRole) {
|
||||
throw new Error("Failed. Cannot set user's role.");
|
||||
} else {
|
||||
if (Array.isArray(currentRole)) await removeUserRoles(userId, currentRole);
|
||||
}
|
||||
|
||||
userRole = role.name;
|
||||
}
|
||||
|
||||
if (body.username) {
|
||||
await editUser(userId, { username: body.username });
|
||||
}
|
||||
|
||||
const { provinceId, districtId, subDistrictId, ...rest } = body;
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
const lastUserOfType =
|
||||
body.userType &&
|
||||
body.userType !== user.userType &&
|
||||
user.code &&
|
||||
(await prisma.user.findFirst({
|
||||
orderBy: { createdAt: "desc" },
|
||||
where: {
|
||||
userType: body.userType,
|
||||
code: { startsWith: `${user.code?.slice(0, 3)}` },
|
||||
},
|
||||
}));
|
||||
|
||||
const record = await prisma.user.update({
|
||||
include: { province: true, district: true, subDistrict: true },
|
||||
data: {
|
||||
...rest,
|
||||
userRole,
|
||||
code:
|
||||
(lastUserOfType &&
|
||||
`${user.code?.slice(0, 3)}${body.userType !== "USER" ? body.userType?.charAt(0) : ""}${(+(lastUserOfType?.code?.slice(-4) || 0) + 1).toString().padStart(4, "0")}`) ||
|
||||
undefined,
|
||||
province: {
|
||||
connect: provinceId ? { id: provinceId } : undefined,
|
||||
disconnect: provinceId === null || undefined,
|
||||
},
|
||||
district: {
|
||||
connect: districtId ? { id: districtId } : undefined,
|
||||
disconnect: districtId === null || undefined,
|
||||
},
|
||||
subDistrict: {
|
||||
connect: subDistrictId ? { id: subDistrictId } : undefined,
|
||||
disconnect: subDistrictId === null || undefined,
|
||||
},
|
||||
updateBy: req.user.name,
|
||||
},
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
return Object.assign(record, {
|
||||
profileImageUrl: await minio.presignedGetObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
profileImageUploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
imageLocation(record.id),
|
||||
12 * 60 * 60,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@Delete("{userId}")
|
||||
async deleteUser(@Path() userId: string) {
|
||||
const record = await prisma.user.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
if (record.status !== Status.CREATED) {
|
||||
throw new HttpError(HttpStatus.FORBIDDEN, "User is in used.", "data_in_used");
|
||||
}
|
||||
|
||||
await minio.removeObject(MINIO_BUCKET, imageLocation(userId), {
|
||||
forceDelete: true,
|
||||
});
|
||||
|
||||
new Promise<string[]>((resolve, reject) => {
|
||||
const item: string[] = [];
|
||||
|
||||
const stream = minio.listObjectsV2(MINIO_BUCKET, `${attachmentLocation(userId)}/`);
|
||||
|
||||
stream.on("data", (v) => v && v.name && item.push(v.name));
|
||||
stream.on("end", () => resolve(item));
|
||||
stream.on("error", () => reject(new Error("MinIO error.")));
|
||||
}).then((list) => {
|
||||
list.map(async (v) => {
|
||||
await minio.removeObject(MINIO_BUCKET, `${attachmentLocation(userId)}/${v}`, {
|
||||
forceDelete: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await deleteUser(userId);
|
||||
|
||||
return await prisma.user.delete({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: userId },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function attachmentLocation(uid: string) {
|
||||
return `user-attachment/${uid}`;
|
||||
}
|
||||
|
||||
@Route("api/user/{userId}/attachment")
|
||||
@Tags("User")
|
||||
@Security("keycloak")
|
||||
export class UserAttachmentController extends Controller {
|
||||
@Get()
|
||||
async listAttachment(@Path() userId: string) {
|
||||
const record = await prisma.user.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
const list = await new Promise<string[]>((resolve, reject) => {
|
||||
const item: string[] = [];
|
||||
|
||||
const stream = minio.listObjectsV2(MINIO_BUCKET, `${attachmentLocation(userId)}/`);
|
||||
|
||||
stream.on("data", (v) => v && v.name && item.push(v.name));
|
||||
stream.on("end", () => resolve(item));
|
||||
stream.on("error", () => reject(new Error("MinIO error.")));
|
||||
});
|
||||
|
||||
return await Promise.all(
|
||||
list.map(async (v) => ({
|
||||
name: v.split("/").at(-1) as string,
|
||||
url: await minio.presignedGetObject(MINIO_BUCKET, v, 12 * 60 * 60),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async addAttachment(@Path() userId: string, @Body() payload: { file: string[] }) {
|
||||
const record = await prisma.user.findFirst({
|
||||
include: {
|
||||
province: true,
|
||||
district: true,
|
||||
subDistrict: true,
|
||||
},
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "data_not_found");
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
payload.file.map(async (v) => ({
|
||||
name: v,
|
||||
url: await minio.presignedGetObject(MINIO_BUCKET, `${attachmentLocation(userId)}/${v}`),
|
||||
uploadUrl: await minio.presignedPutObject(
|
||||
MINIO_BUCKET,
|
||||
`${attachmentLocation(userId)}/${v}`,
|
||||
12 * 60 * 60,
|
||||
),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
async deleteAttachment(@Path() userId: string, @Body() payload: { file: string[] }) {
|
||||
await Promise.all(
|
||||
payload.file.map(async (v) => {
|
||||
await minio.removeObject(MINIO_BUCKET, `${attachmentLocation(userId)}/${v}`, {
|
||||
forceDelete: true,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,29 @@
|
|||
import HttpStatus from "./http-status";
|
||||
|
||||
type DevMessage =
|
||||
| "missing_or_invalid_parameter"
|
||||
| "data_exists"
|
||||
| "data_in_used"
|
||||
| "no_permission"
|
||||
| "unknown_url"
|
||||
| "data_not_found"
|
||||
| "unauthorized";
|
||||
|
||||
class HttpError extends Error {
|
||||
/**
|
||||
* HTTP Status Code
|
||||
*/
|
||||
status: HttpStatus;
|
||||
message: string;
|
||||
devMessage?: DevMessage;
|
||||
|
||||
constructor(status: HttpStatus, message: string) {
|
||||
constructor(status: HttpStatus, message: string, devMessage?: DevMessage) {
|
||||
super(message);
|
||||
|
||||
this.name = "HttpError";
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.devMessage = devMessage;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,11 +16,7 @@ const jwtVerify = createVerifier({
|
|||
|
||||
const jwtDecode = createDecoder();
|
||||
|
||||
export async function keycloakAuth(
|
||||
request: Express.Request,
|
||||
_securityName?: string,
|
||||
_scopes?: string[],
|
||||
) {
|
||||
export async function keycloakAuth(request: Express.Request, roles?: string[]) {
|
||||
const token = request.headers["authorization"]?.includes("Bearer ")
|
||||
? request.headers["authorization"].split(" ")[1]
|
||||
: request.headers["authorization"];
|
||||
|
|
@ -39,7 +35,7 @@ export async function keycloakAuth(
|
|||
payload = await verifyOffline(token);
|
||||
break;
|
||||
default:
|
||||
if (process.env.KC_REALM_URL) {
|
||||
if (process.env.KC_URL) {
|
||||
payload = await verifyOnline(token);
|
||||
break;
|
||||
}
|
||||
|
|
@ -49,6 +45,12 @@ export async function keycloakAuth(
|
|||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(payload.roles) && Array.isArray(roles) && roles.length > 0) {
|
||||
if (!roles.some((a: string) => payload.roles.includes(a))) {
|
||||
throw new HttpError(HttpStatus.FORBIDDEN, "คุณไม่มีสิทธิในการเข้าถึงข้อมูลดังกล่าว");
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import { keycloakAuth } from "./auth-provider/keycloak";
|
|||
export async function expressAuthentication(
|
||||
request: Express.Request,
|
||||
securityName: string,
|
||||
_scopes?: string[],
|
||||
scopes?: string[],
|
||||
) {
|
||||
switch (securityName) {
|
||||
case "keycloak":
|
||||
return keycloakAuth(request);
|
||||
return keycloakAuth(request, scopes);
|
||||
default:
|
||||
throw new HttpError(HttpStatus.NOT_IMPLEMENTED, "ไม่ทราบวิธียืนยันตัวตน");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ function error(error: Error, _req: Request, res: Response, _next: NextFunction)
|
|||
return res.status(error.status).json({
|
||||
status: error.status,
|
||||
message: error.message,
|
||||
devMessage: error.devMessage,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -16,6 +17,7 @@ function error(error: Error, _req: Request, res: Response, _next: NextFunction)
|
|||
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
message: "Validation error(s).",
|
||||
detail: error.fields,
|
||||
devMessage: "missing_or_invalid_parameter",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +26,7 @@ function error(error: Error, _req: Request, res: Response, _next: NextFunction)
|
|||
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
message: error.message,
|
||||
devMessage: "system_error",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
79
src/middlewares/log.ts
Normal file
79
src/middlewares/log.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { NextFunction, Request, Response } from "express";
|
||||
import elasticsearch from "../services/elasticsearch";
|
||||
|
||||
if (!process.env.ELASTICSEARCH_INDEX) {
|
||||
throw new Error("Require ELASTICSEARCH_INDEX to store log.");
|
||||
}
|
||||
|
||||
const ELASTICSEARCH_INDEX = process.env.ELASTICSEARCH_INDEX;
|
||||
|
||||
const LOG_LEVEL_MAP: Record<string, number> = {
|
||||
debug: 4,
|
||||
info: 3,
|
||||
warning: 2,
|
||||
error: 1,
|
||||
none: 0,
|
||||
};
|
||||
|
||||
async function logMiddleware(req: Request, res: Response, next: NextFunction) {
|
||||
if (!req.url.startsWith("/api/")) return next();
|
||||
|
||||
let data: any;
|
||||
|
||||
const originalJson = res.json;
|
||||
|
||||
res.json = function (v: any) {
|
||||
data = v;
|
||||
return originalJson.call(this, v);
|
||||
};
|
||||
|
||||
const timestamp = new Date().toString();
|
||||
const start = performance.now();
|
||||
|
||||
req.app.locals.logData = {};
|
||||
|
||||
res.on("finish", () => {
|
||||
if (!req.url.startsWith("/api/")) return;
|
||||
|
||||
const level = LOG_LEVEL_MAP[process.env.LOG_LEVEL ?? "info"] || 1;
|
||||
|
||||
if (level === 1 && res.statusCode < 500) return;
|
||||
if (level === 2 && res.statusCode < 400) return;
|
||||
if (level === 3 && res.statusCode < 200) return;
|
||||
|
||||
const obj = {
|
||||
logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info",
|
||||
systemName: "JWS-SOS",
|
||||
startTimeStamp: timestamp,
|
||||
endTimeStamp: new Date().toString(),
|
||||
processTime: performance.now() - start,
|
||||
host: req.hostname,
|
||||
sessionId: req.headers["x-session-id"],
|
||||
rtId: req.headers["x-rtid"],
|
||||
tId: req.headers["x-tid"],
|
||||
method: req.method,
|
||||
endpoint: req.url,
|
||||
responseCode: res.statusCode,
|
||||
responseDescription:
|
||||
data?.devMessage !== undefined
|
||||
? data.devMessage
|
||||
: { 200: "success", 201: "created_success", 204: "no_content", 304: "success" }[
|
||||
res.statusCode
|
||||
],
|
||||
input: (level === 4 && JSON.stringify(req.body, null, 2)) || undefined,
|
||||
output: (level === 4 && JSON.stringify(data, null, 2)) || undefined,
|
||||
...req.app.locals.logData,
|
||||
};
|
||||
|
||||
console.log(obj);
|
||||
|
||||
elasticsearch.index({
|
||||
index: ELASTICSEARCH_INDEX,
|
||||
document: obj,
|
||||
});
|
||||
});
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
export default logMiddleware;
|
||||
7
src/services/elasticsearch.ts
Normal file
7
src/services/elasticsearch.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Client } from "@elastic/elasticsearch";
|
||||
|
||||
const elasticsearch = new Client({
|
||||
node: `${process.env.ELASTICSEARCH_PROTOCOL}://${process.env.ELASTICSEARCH_HOST}:${process.env.ELASTICSEARCH_PORT}`,
|
||||
});
|
||||
|
||||
export default elasticsearch;
|
||||
|
|
@ -91,6 +91,63 @@ export async function createUser(username: string, password: string, opts?: Reco
|
|||
return id || true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update keycloak user by uuid
|
||||
*
|
||||
* Client must have permission to manage realm's user
|
||||
*
|
||||
* @returns user uuid or true if success, false otherwise.
|
||||
*/
|
||||
export async function editUser(userId: string, opts: Record<string, any>) {
|
||||
const { password, ...rest } = opts;
|
||||
|
||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}`, {
|
||||
// prettier-ignore
|
||||
headers: {
|
||||
"authorization": `Bearer ${await getToken()}`,
|
||||
"content-type": `application/json`,
|
||||
},
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
enabled: true,
|
||||
credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
|
||||
...rest,
|
||||
}),
|
||||
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||
|
||||
if (!res) return false;
|
||||
if (!res.ok) {
|
||||
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||
}
|
||||
|
||||
const path = res.headers.get("Location");
|
||||
const id = path?.split("/").at(-1);
|
||||
return id || true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete keycloak user by uuid
|
||||
*
|
||||
* Client must have permission to manage realm's user
|
||||
*
|
||||
* @returns user uuid or true if success, false otherwise.
|
||||
*/
|
||||
export async function deleteUser(userId: string) {
|
||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}`, {
|
||||
// prettier-ignore
|
||||
headers: {
|
||||
"authorization": `Bearer ${await getToken()}`,
|
||||
"content-type": `application/json`,
|
||||
},
|
||||
method: "DELETE",
|
||||
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||
|
||||
if (!res) return false;
|
||||
if (!res.ok) {
|
||||
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get roles list or specific role data
|
||||
*
|
||||
|
|
@ -121,7 +178,46 @@ export async function getRoles(name?: string) {
|
|||
const data = await res.json();
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((v: Record<string, any>) => ({ id: v.id, name: v.name }));
|
||||
return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
|
||||
}
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get roles list of user
|
||||
*
|
||||
* Client must have permission to get realms roles
|
||||
*
|
||||
* @returns role's info (array if not specify name) if success, null if not found, false otherwise.
|
||||
*/
|
||||
export async function getUserRoles(userId: string) {
|
||||
const res = await fetch(
|
||||
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
||||
{
|
||||
// prettier-ignore
|
||||
headers: {
|
||||
"authorization": `Bearer ${await getToken()}`,
|
||||
},
|
||||
},
|
||||
).catch((e) => console.log(e));
|
||||
|
||||
if (!res) return false;
|
||||
if (!res.ok && res.status !== 404) {
|
||||
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||
}
|
||||
|
||||
if (res.status === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -137,7 +233,7 @@ export async function getRoles(name?: string) {
|
|||
*
|
||||
* @returns true if success, false otherwise.
|
||||
*/
|
||||
export async function addUserRoles(userId: string, roleId: string[]) {
|
||||
export async function addUserRoles(userId: string, roles: { id: string; name: string }[]) {
|
||||
const res = await fetch(
|
||||
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
||||
{
|
||||
|
|
@ -147,7 +243,7 @@ export async function addUserRoles(userId: string, roleId: string[]) {
|
|||
"content-type": `application/json`,
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify(roleId.map((v) => ({ id: v }))),
|
||||
body: JSON.stringify(roles),
|
||||
},
|
||||
).catch((e) => console.log(e));
|
||||
|
||||
|
|
@ -165,7 +261,7 @@ export async function addUserRoles(userId: string, roleId: string[]) {
|
|||
*
|
||||
* @returns true if success, false otherwise.
|
||||
*/
|
||||
export async function removeUserRoles(userId: string, roleId: string[]) {
|
||||
export async function removeUserRoles(userId: string, roles: { id: string; name: string }[]) {
|
||||
const res = await fetch(
|
||||
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
||||
{
|
||||
|
|
@ -175,7 +271,7 @@ export async function removeUserRoles(userId: string, roleId: string[]) {
|
|||
"content-type": `application/json`,
|
||||
},
|
||||
method: "DELETE",
|
||||
body: JSON.stringify(roleId.map((v) => ({ id: v }))),
|
||||
body: JSON.stringify(roles),
|
||||
},
|
||||
).catch((e) => console.log(e));
|
||||
|
||||
|
|
|
|||
22
tsoa.json
22
tsoa.json
|
|
@ -12,6 +12,28 @@
|
|||
"description": "Keycloak Bearer Token",
|
||||
"in": "header"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"tags": [
|
||||
{ "name": "OpenAPI" },
|
||||
{ "name": "Single-Sign On" },
|
||||
{ "name": "Permission" },
|
||||
{ "name": "Address" },
|
||||
{ "name": "Branch" },
|
||||
{ "name": "Branch Contact" },
|
||||
{ "name": "User" },
|
||||
{ "name": "Branch User" },
|
||||
{ "name": "Customer" },
|
||||
{ "name": "Customer Branch" },
|
||||
{ "name": "Employee" },
|
||||
{ "name": "Employee Checkup" },
|
||||
{ "name": "Employee Work" },
|
||||
{ "name": "Employee Other Info" },
|
||||
{ "name": "Service" },
|
||||
{ "name": "Work" },
|
||||
{ "name": "Product Type" },
|
||||
{ "name": "Product Group" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue