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_HOST=0.0.0.0
|
||||||
APP_PORT=3000
|
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
|
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"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
RUN apt-get update && apt-get install -y openssl
|
||||||
|
RUN pnpm i -g prisma
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
@ -11,6 +13,7 @@ COPY . .
|
||||||
|
|
||||||
FROM base AS deps
|
FROM base AS deps
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||||
|
RUN pnpm prisma generate
|
||||||
|
|
||||||
FROM base AS build
|
FROM base AS build
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
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
|
RUN pnpm run build
|
||||||
|
|
||||||
FROM base as prod
|
FROM base as prod
|
||||||
|
|
||||||
ENV NODE_ENV="production"
|
ENV NODE_ENV="production"
|
||||||
|
|
||||||
COPY --from=deps /app/node_modules /app/node_modules
|
COPY --from=deps /app/node_modules /app/node_modules
|
||||||
COPY --from=build /app/dist /app/dist
|
COPY --from=build /app/dist /app/dist
|
||||||
COPY --from=base /app/static /app/static
|
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",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./dist/app.js",
|
"main": "./dist/app.js",
|
||||||
|
|
@ -24,12 +24,13 @@
|
||||||
"@types/swagger-ui-express": "^4.1.6",
|
"@types/swagger-ui-express": "^4.1.6",
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prisma": "^5.11.0",
|
"prisma": "^5.12.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.3"
|
"typescript": "^5.4.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "5.11.0",
|
"@elastic/elasticsearch": "^8.13.0",
|
||||||
|
"@prisma/client": "5.12.1",
|
||||||
"@tsoa/runtime": "^6.2.0",
|
"@tsoa/runtime": "^6.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
|
|
||||||
105
pnpm-lock.yaml
generated
105
pnpm-lock.yaml
generated
|
|
@ -5,9 +5,12 @@ settings:
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@elastic/elasticsearch':
|
||||||
|
specifier: ^8.13.0
|
||||||
|
version: 8.13.0
|
||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: 5.11.0
|
specifier: 5.12.1
|
||||||
version: 5.11.0(prisma@5.11.0)
|
version: 5.12.1(prisma@5.12.1)
|
||||||
'@tsoa/runtime':
|
'@tsoa/runtime':
|
||||||
specifier: ^6.2.0
|
specifier: ^6.2.0
|
||||||
version: 6.2.0
|
version: 6.2.0
|
||||||
|
|
@ -56,8 +59,8 @@ devDependencies:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
prisma:
|
prisma:
|
||||||
specifier: ^5.11.0
|
specifier: ^5.12.1
|
||||||
version: 5.11.0
|
version: 5.12.1
|
||||||
ts-node:
|
ts-node:
|
||||||
specifier: ^10.9.2
|
specifier: ^10.9.2
|
||||||
version: 10.9.2(@types/node@20.12.2)(typescript@5.4.3)
|
version: 10.9.2(@types/node@20.12.2)(typescript@5.4.3)
|
||||||
|
|
@ -74,6 +77,30 @@ packages:
|
||||||
'@jridgewell/trace-mapping': 0.3.9
|
'@jridgewell/trace-mapping': 0.3.9
|
||||||
dev: true
|
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:
|
/@hapi/accept@6.0.3:
|
||||||
resolution: {integrity: sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==}
|
resolution: {integrity: sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -339,8 +366,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@prisma/client@5.11.0(prisma@5.11.0):
|
/@prisma/client@5.12.1(prisma@5.12.1):
|
||||||
resolution: {integrity: sha512-SWshvS5FDXvgJKM/a0y9nDC1rqd7KG0Q6ZVzd+U7ZXK5soe73DJxJJgbNBt2GNXOa+ysWB4suTpdK5zfFPhwiw==}
|
resolution: {integrity: sha512-6/JnizEdlSBxDIdiLbrBdMW5NqDxOmhXAJaNXiPpgzAPr/nLZResT6MMpbOHLo5yAbQ1Vv5UU8PTPRzb0WIxdA==}
|
||||||
engines: {node: '>=16.13'}
|
engines: {node: '>=16.13'}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -349,35 +376,35 @@ packages:
|
||||||
prisma:
|
prisma:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
prisma: 5.11.0
|
prisma: 5.12.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@prisma/debug@5.11.0:
|
/@prisma/debug@5.12.1:
|
||||||
resolution: {integrity: sha512-N6yYr3AbQqaiUg+OgjkdPp3KPW1vMTAgtKX6+BiB/qB2i1TjLYCrweKcUjzOoRM5BriA4idrkTej9A9QqTfl3A==}
|
resolution: {integrity: sha512-kd/wNsR0klrv79o1ITsbWxYyh4QWuBidvxsXSParPsYSu0ircUmNk3q4ojsgNc3/81b0ozg76iastOG43tbf8A==}
|
||||||
|
|
||||||
/@prisma/engines-version@5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102:
|
/@prisma/engines-version@5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab:
|
||||||
resolution: {integrity: sha512-WXCuyoymvrS4zLz4wQagSsc3/nE6CHy8znyiMv8RKazKymOMd5o9FP5RGwGHAtgoxd+aB/BWqxuP/Ckfu7/3MA==}
|
resolution: {integrity: sha512-6yvO8s80Tym61aB4QNtYZfWVmE3pwqe807jEtzm8C5VDe7nw8O1FGX3TXUaXmWV0fQTIAfRbeL2Gwrndabp/0g==}
|
||||||
|
|
||||||
/@prisma/engines@5.11.0:
|
/@prisma/engines@5.12.1:
|
||||||
resolution: {integrity: sha512-gbrpQoBTYWXDRqD+iTYMirDlF9MMlQdxskQXbhARhG6A/uFQjB7DZMYocMQLoiZXO/IskfDOZpPoZE8TBQKtEw==}
|
resolution: {integrity: sha512-HQDdglLw2bZR/TXD2Y+YfDMvi5Q8H+acbswqOsWyq9pPjBLYJ6gzM+ptlTU/AV6tl0XSZLU1/7F4qaWa8bqpJA==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/debug': 5.11.0
|
'@prisma/debug': 5.12.1
|
||||||
'@prisma/engines-version': 5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102
|
'@prisma/engines-version': 5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab
|
||||||
'@prisma/fetch-engine': 5.11.0
|
'@prisma/fetch-engine': 5.12.1
|
||||||
'@prisma/get-platform': 5.11.0
|
'@prisma/get-platform': 5.12.1
|
||||||
|
|
||||||
/@prisma/fetch-engine@5.11.0:
|
/@prisma/fetch-engine@5.12.1:
|
||||||
resolution: {integrity: sha512-994viazmHTJ1ymzvWugXod7dZ42T2ROeFuH6zHPcUfp/69+6cl5r9u3NFb6bW8lLdNjwLYEVPeu3hWzxpZeC0w==}
|
resolution: {integrity: sha512-qSs3KcX1HKcea1A+hlJVK/ljj0PNIUHDxAayGMvgJBqmaN32P9tCidlKz1EGv6WoRFICYnk3Dd/YFLBwnFIozA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/debug': 5.11.0
|
'@prisma/debug': 5.12.1
|
||||||
'@prisma/engines-version': 5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102
|
'@prisma/engines-version': 5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab
|
||||||
'@prisma/get-platform': 5.11.0
|
'@prisma/get-platform': 5.12.1
|
||||||
|
|
||||||
/@prisma/get-platform@5.11.0:
|
/@prisma/get-platform@5.12.1:
|
||||||
resolution: {integrity: sha512-rxtHpMLxNTHxqWuGOLzR2QOyQi79rK1u1XYAVLZxDGTLz/A+uoDnjz9veBFlicrpWjwuieM4N6jcnjj/DDoidw==}
|
resolution: {integrity: sha512-pgIR+pSvhYHiUcqXVEZS31NrFOTENC9yFUdEAcx7cdQBoZPmHVjtjN4Ss6NzVDMYPrKJJ51U14EhEoeuBlMioQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/debug': 5.11.0
|
'@prisma/debug': 5.12.1
|
||||||
|
|
||||||
/@tsconfig/node10@1.0.11:
|
/@tsconfig/node10@1.0.11:
|
||||||
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
|
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
|
||||||
|
|
@ -892,7 +919,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
supports-color: 5.5.0
|
supports-color: 5.5.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/decode-uri-component@0.2.2:
|
/decode-uri-component@0.2.2:
|
||||||
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
||||||
|
|
@ -1322,7 +1348,6 @@ packages:
|
||||||
/has-flag@3.0.0:
|
/has-flag@3.0.0:
|
||||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/has-property-descriptors@1.0.2:
|
/has-property-descriptors@1.0.2:
|
||||||
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
|
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
|
||||||
|
|
@ -1354,6 +1379,11 @@ packages:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/hpagent@1.2.0:
|
||||||
|
resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/http-errors@2.0.0:
|
/http-errors@2.0.0:
|
||||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
@ -1715,7 +1745,6 @@ packages:
|
||||||
|
|
||||||
/ms@2.1.2:
|
/ms@2.1.2:
|
||||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ms@2.1.3:
|
/ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
@ -1832,13 +1861,13 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/prisma@5.11.0:
|
/prisma@5.12.1:
|
||||||
resolution: {integrity: sha512-KCLiug2cs0Je7kGkQBN9jDWoZ90ogE/kvZTUTgz2h94FEo8pczCkPH7fPNXkD1sGU7Yh65risGGD1HQ5DF3r3g==}
|
resolution: {integrity: sha512-SkMnb6wyIxTv9ACqiHBI2u9gD6y98qXRoCoLEnZsF6yee5Qg828G+ARrESN+lQHdw4maSZFFSBPPDpvSiVTo0Q==}
|
||||||
engines: {node: '>=16.13'}
|
engines: {node: '>=16.13'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/engines': 5.11.0
|
'@prisma/engines': 5.12.1
|
||||||
|
|
||||||
/promise.any@2.0.6:
|
/promise.any@2.0.6:
|
||||||
resolution: {integrity: sha512-Ew/MrPtTjiHnnki0AA2hS2o65JaZ5n+5pp08JSyWWUdeOGF4F41P+Dn+rdqnaOV/FTxhR6eBDX412luwn3th9g==}
|
resolution: {integrity: sha512-Ew/MrPtTjiHnnki0AA2hS2o65JaZ5n+5pp08JSyWWUdeOGF4F41P+Dn+rdqnaOV/FTxhR6eBDX412luwn3th9g==}
|
||||||
|
|
@ -1963,6 +1992,10 @@ packages:
|
||||||
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
|
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/secure-json-parse@2.7.0:
|
||||||
|
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/semver@7.6.0:
|
/semver@7.6.0:
|
||||||
resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
|
resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
@ -2165,7 +2198,6 @@ packages:
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 3.0.0
|
has-flag: 3.0.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/swagger-ui-dist@5.13.0:
|
/swagger-ui-dist@5.13.0:
|
||||||
resolution: {integrity: sha512-uaWhh6j18IIs5tOX0arvIBnVINAzpTXaQXkr7qAk8zoupegJVg0UU/5+S/FgsgVCnzVsJ9d7QLjIxkswEeTg0Q==}
|
resolution: {integrity: sha512-uaWhh6j18IIs5tOX0arvIBnVINAzpTXaQXkr7qAk8zoupegJVg0UU/5+S/FgsgVCnzVsJ9d7QLjIxkswEeTg0Q==}
|
||||||
|
|
@ -2242,6 +2274,10 @@ packages:
|
||||||
yn: 3.1.1
|
yn: 3.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tslib@2.6.2:
|
||||||
|
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tsoa@6.2.0:
|
/tsoa@6.2.0:
|
||||||
resolution: {integrity: sha512-EX/RyoU+4hD1rLM5NjYG+I7lEhqx1yuLgcHs/gyWQpkX/RL9cVR9hFA9LKQrK6PE+WTg1SEahn1MK3l/+6pVKw==}
|
resolution: {integrity: sha512-EX/RyoU+4hD1rLM5NjYG+I7lEhqx1yuLgcHs/gyWQpkX/RL9cVR9hFA9LKQrK6PE+WTg1SEahn1MK3l/+6pVKw==}
|
||||||
engines: {node: '>=18.0.0', yarn: '>=1.9.4'}
|
engines: {node: '>=18.0.0', yarn: '>=1.9.4'}
|
||||||
|
|
@ -2334,6 +2370,11 @@ packages:
|
||||||
/undici-types@5.26.5:
|
/undici-types@5.26.5:
|
||||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
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:
|
/universalify@2.0.1:
|
||||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
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")
|
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 {
|
model Province {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
nameTH String
|
name String
|
||||||
nameEN String
|
nameEN String
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
|
|
@ -27,7 +130,7 @@ model Province {
|
||||||
|
|
||||||
model District {
|
model District {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
nameTH String
|
name String
|
||||||
nameEN String
|
nameEN String
|
||||||
|
|
||||||
provinceId String
|
provinceId String
|
||||||
|
|
@ -47,7 +150,7 @@ model District {
|
||||||
|
|
||||||
model SubDistrict {
|
model SubDistrict {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
nameTH String
|
name String
|
||||||
nameEN String
|
nameEN String
|
||||||
zipCode String
|
zipCode String
|
||||||
|
|
||||||
|
|
@ -65,14 +168,21 @@ model SubDistrict {
|
||||||
employee Employee[]
|
employee Employee[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
CREATED
|
||||||
|
ACTIVE
|
||||||
|
INACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
model Branch {
|
model Branch {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
code String
|
code String
|
||||||
taxNo String
|
taxNo String
|
||||||
nameTH String
|
name String
|
||||||
nameEN String
|
nameEN String
|
||||||
addressTH String
|
address String
|
||||||
addressEN String
|
addressEN String
|
||||||
|
telephoneNo String
|
||||||
|
|
||||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||||
provinceId String?
|
provinceId String?
|
||||||
|
|
@ -86,7 +196,8 @@ model Branch {
|
||||||
zipCode String
|
zipCode String
|
||||||
|
|
||||||
email String
|
email String
|
||||||
telephoneNo String
|
contactName String?
|
||||||
|
lineId String?
|
||||||
|
|
||||||
latitude String
|
latitude String
|
||||||
longitude String
|
longitude String
|
||||||
|
|
@ -96,7 +207,7 @@ model Branch {
|
||||||
headOffice Branch? @relation(name: "HeadOfficeRelation", fields: [headOfficeId], references: [id])
|
headOffice Branch? @relation(name: "HeadOfficeRelation", fields: [headOfficeId], references: [id])
|
||||||
headOfficeId String?
|
headOfficeId String?
|
||||||
|
|
||||||
status String?
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
@ -109,10 +220,8 @@ model Branch {
|
||||||
}
|
}
|
||||||
|
|
||||||
model BranchContact {
|
model BranchContact {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
telephoneNo String
|
telephoneNo String
|
||||||
lineId String
|
|
||||||
qrCodeImageUrl String?
|
|
||||||
|
|
||||||
branch Branch @relation(fields: [branchId], references: [id], onDelete: Cascade)
|
branch Branch @relation(fields: [branchId], references: [id], onDelete: Cascade)
|
||||||
branchId String
|
branchId String
|
||||||
|
|
@ -138,16 +247,25 @@ model BranchUser {
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum UserType {
|
||||||
|
USER
|
||||||
|
MESSENGER
|
||||||
|
DELEGATE
|
||||||
|
AGENCY
|
||||||
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
code String
|
code String?
|
||||||
firstNameTH String
|
firstName String
|
||||||
firstNameEN String
|
firstNameEN String
|
||||||
lastNameTH String
|
lastName String
|
||||||
lastNameEN String
|
lastNameEN String
|
||||||
|
username String
|
||||||
|
gender String
|
||||||
|
|
||||||
addressTH String
|
address String
|
||||||
addressEN String
|
addressEN String
|
||||||
|
|
||||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||||
|
|
@ -164,46 +282,56 @@ model User {
|
||||||
email String
|
email String
|
||||||
telephoneNo String
|
telephoneNo String
|
||||||
|
|
||||||
registrationNo String
|
registrationNo String?
|
||||||
|
|
||||||
startDate DateTime
|
startDate DateTime?
|
||||||
retireDate DateTime
|
retireDate DateTime?
|
||||||
|
|
||||||
profileImageUrl String?
|
checkpoint String?
|
||||||
|
checkpointEN String?
|
||||||
|
|
||||||
userType String
|
userType UserType
|
||||||
userRole String
|
userRole String
|
||||||
|
|
||||||
discountCondition String
|
discountCondition String?
|
||||||
|
|
||||||
licenseNo String
|
licenseNo String?
|
||||||
licenseIssueDate DateTime
|
licenseIssueDate DateTime?
|
||||||
licenseExpireDate DateTime
|
licenseExpireDate DateTime?
|
||||||
|
|
||||||
sourceNationality String
|
sourceNationality String?
|
||||||
importNationality String
|
importNationality String?
|
||||||
|
|
||||||
trainingPlace String
|
trainingPlace String?
|
||||||
|
responsibleArea String?
|
||||||
|
|
||||||
status String?
|
birthDate DateTime?
|
||||||
|
|
||||||
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updateBy String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
branch BranchUser[]
|
branch BranchUser[]
|
||||||
|
userMenuPermission UserMenuPermission[]
|
||||||
|
userMenuComponentPermission UserMenuComponentPermission[]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CustomerType {
|
||||||
|
CORP
|
||||||
|
PERS
|
||||||
}
|
}
|
||||||
|
|
||||||
model Customer {
|
model Customer {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
code String
|
code String
|
||||||
customerType String
|
customerType CustomerType
|
||||||
customerNameTH String
|
customerName String
|
||||||
customerNameEN String
|
customerNameEN String
|
||||||
imageUrl String?
|
|
||||||
|
|
||||||
status String?
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
@ -218,7 +346,7 @@ model CustomerBranch {
|
||||||
branchNo String
|
branchNo String
|
||||||
legalPersonNo String
|
legalPersonNo String
|
||||||
|
|
||||||
nameTH String
|
name String
|
||||||
nameEN String
|
nameEN String
|
||||||
|
|
||||||
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||||
|
|
@ -229,6 +357,7 @@ model CustomerBranch {
|
||||||
registerDate DateTime
|
registerDate DateTime
|
||||||
authorizedCapital String
|
authorizedCapital String
|
||||||
|
|
||||||
|
address String
|
||||||
addressEN String
|
addressEN String
|
||||||
|
|
||||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||||
|
|
@ -248,6 +377,8 @@ model CustomerBranch {
|
||||||
latitude String
|
latitude String
|
||||||
longitude String
|
longitude String
|
||||||
|
|
||||||
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updateBy String?
|
||||||
|
|
@ -260,13 +391,17 @@ model Employee {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
code String
|
code String
|
||||||
fullNameTH String
|
nrcNo String
|
||||||
fullNameEN String
|
firstName String
|
||||||
|
firstNameEN String
|
||||||
|
lastName String
|
||||||
|
lastNameEN String
|
||||||
|
|
||||||
dateOfBirth DateTime
|
dateOfBirth DateTime
|
||||||
gender String
|
gender String
|
||||||
nationality String
|
nationality String
|
||||||
|
|
||||||
addressTH String
|
address String
|
||||||
addressEN String
|
addressEN String
|
||||||
|
|
||||||
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
|
||||||
|
|
@ -285,12 +420,11 @@ model Employee {
|
||||||
|
|
||||||
arrivalBarricade String
|
arrivalBarricade String
|
||||||
arrivalCardNo String
|
arrivalCardNo String
|
||||||
profileImageUrl String
|
|
||||||
|
|
||||||
customerBranch CustomerBranch? @relation(fields: [customerBranchId], references: [id], onDelete: SetNull)
|
customerBranch CustomerBranch? @relation(fields: [customerBranchId], references: [id], onDelete: SetNull)
|
||||||
customerBranchId String?
|
customerBranchId String?
|
||||||
|
|
||||||
status String?
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
@ -299,7 +433,7 @@ model Employee {
|
||||||
|
|
||||||
employeeCheckup EmployeeCheckup[]
|
employeeCheckup EmployeeCheckup[]
|
||||||
employeeWork EmployeeWork[]
|
employeeWork EmployeeWork[]
|
||||||
EmployeeOtherInfo EmployeeOtherInfo[]
|
employeeOtherInfo EmployeeOtherInfo[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model EmployeeCheckup {
|
model EmployeeCheckup {
|
||||||
|
|
@ -368,11 +502,11 @@ model EmployeeOtherInfo {
|
||||||
model Service {
|
model Service {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
code String
|
code String
|
||||||
name String
|
name String
|
||||||
detail String
|
detail String
|
||||||
imageUrl String
|
|
||||||
status String?
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
@ -384,12 +518,14 @@ model Service {
|
||||||
model Work {
|
model Work {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
order String
|
order Int
|
||||||
name String
|
name String
|
||||||
|
|
||||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||||
serviceId String
|
serviceId String
|
||||||
|
|
||||||
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updateBy String?
|
updateBy String?
|
||||||
|
|
@ -416,7 +552,8 @@ model ProductGroup {
|
||||||
name String
|
name String
|
||||||
detail String
|
detail String
|
||||||
remark String
|
remark String
|
||||||
status String?
|
|
||||||
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
@ -432,7 +569,8 @@ model ProductType {
|
||||||
name String
|
name String
|
||||||
detail String
|
detail String
|
||||||
remark String
|
remark String
|
||||||
status String?
|
|
||||||
|
status Status @default(CREATED)
|
||||||
|
|
||||||
createdBy String?
|
createdBy String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
@ -453,7 +591,8 @@ model Product {
|
||||||
agentPrice Int
|
agentPrice Int
|
||||||
serviceCharge Int
|
serviceCharge Int
|
||||||
imageUrl String
|
imageUrl String
|
||||||
status String?
|
|
||||||
|
status Status @default(CREATED)
|
||||||
|
|
||||||
productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull)
|
productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull)
|
||||||
productTypeId String?
|
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 swaggerDocument from "./swagger.json";
|
||||||
import error from "./middlewares/error";
|
import error from "./middlewares/error";
|
||||||
import { RegisterRoutes } from "./routes";
|
import { RegisterRoutes } from "./routes";
|
||||||
|
import logMiddleware from "./middlewares/log";
|
||||||
|
|
||||||
const APP_HOST = process.env.APP_HOST || "0.0.0.0";
|
const APP_HOST = process.env.APP_HOST || "0.0.0.0";
|
||||||
const APP_PORT = +(process.env.APP_PORT || 3000);
|
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(cors());
|
||||||
app.use(json());
|
app.use(json());
|
||||||
app.use(urlencoded({ extended: true }));
|
app.use(urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
app.use(logMiddleware);
|
||||||
|
|
||||||
app.use("/", express.static("static"));
|
app.use("/", express.static("static"));
|
||||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
||||||
|
|
||||||
RegisterRoutes(app);
|
RegisterRoutes(app);
|
||||||
|
|
||||||
|
app.get("*", (_, res) =>
|
||||||
|
res.status(404).send({
|
||||||
|
status: 404,
|
||||||
|
message: "Route not found.",
|
||||||
|
devMessage: "unknown_url",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
app.use(error);
|
app.use(error);
|
||||||
|
|
||||||
app.listen(APP_PORT, APP_HOST, () => console.log(`Listening on: http://localhost:${APP_PORT}`));
|
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";
|
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 {
|
class HttpError extends Error {
|
||||||
/**
|
/**
|
||||||
* HTTP Status Code
|
* HTTP Status Code
|
||||||
*/
|
*/
|
||||||
status: HttpStatus;
|
status: HttpStatus;
|
||||||
message: string;
|
message: string;
|
||||||
|
devMessage?: DevMessage;
|
||||||
|
|
||||||
constructor(status: HttpStatus, message: string) {
|
constructor(status: HttpStatus, message: string, devMessage?: DevMessage) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
||||||
this.name = "HttpError";
|
this.name = "HttpError";
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
this.devMessage = devMessage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,7 @@ const jwtVerify = createVerifier({
|
||||||
|
|
||||||
const jwtDecode = createDecoder();
|
const jwtDecode = createDecoder();
|
||||||
|
|
||||||
export async function keycloakAuth(
|
export async function keycloakAuth(request: Express.Request, roles?: string[]) {
|
||||||
request: Express.Request,
|
|
||||||
_securityName?: string,
|
|
||||||
_scopes?: string[],
|
|
||||||
) {
|
|
||||||
const token = request.headers["authorization"]?.includes("Bearer ")
|
const token = request.headers["authorization"]?.includes("Bearer ")
|
||||||
? request.headers["authorization"].split(" ")[1]
|
? request.headers["authorization"].split(" ")[1]
|
||||||
: request.headers["authorization"];
|
: request.headers["authorization"];
|
||||||
|
|
@ -39,7 +35,7 @@ export async function keycloakAuth(
|
||||||
payload = await verifyOffline(token);
|
payload = await verifyOffline(token);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (process.env.KC_REALM_URL) {
|
if (process.env.KC_URL) {
|
||||||
payload = await verifyOnline(token);
|
payload = await verifyOnline(token);
|
||||||
break;
|
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;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import { keycloakAuth } from "./auth-provider/keycloak";
|
||||||
export async function expressAuthentication(
|
export async function expressAuthentication(
|
||||||
request: Express.Request,
|
request: Express.Request,
|
||||||
securityName: string,
|
securityName: string,
|
||||||
_scopes?: string[],
|
scopes?: string[],
|
||||||
) {
|
) {
|
||||||
switch (securityName) {
|
switch (securityName) {
|
||||||
case "keycloak":
|
case "keycloak":
|
||||||
return keycloakAuth(request);
|
return keycloakAuth(request, scopes);
|
||||||
default:
|
default:
|
||||||
throw new HttpError(HttpStatus.NOT_IMPLEMENTED, "ไม่ทราบวิธียืนยันตัวตน");
|
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({
|
return res.status(error.status).json({
|
||||||
status: error.status,
|
status: error.status,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
devMessage: error.devMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,6 +17,7 @@ function error(error: Error, _req: Request, res: Response, _next: NextFunction)
|
||||||
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
message: "Validation error(s).",
|
message: "Validation error(s).",
|
||||||
detail: error.fields,
|
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({
|
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
|
||||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
message: error.message,
|
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;
|
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
|
* Get roles list or specific role data
|
||||||
*
|
*
|
||||||
|
|
@ -121,7 +178,46 @@ export async function getRoles(name?: string) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
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 {
|
return {
|
||||||
|
|
@ -137,7 +233,7 @@ export async function getRoles(name?: string) {
|
||||||
*
|
*
|
||||||
* @returns true if success, false otherwise.
|
* @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(
|
const res = await fetch(
|
||||||
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
`${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`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(roleId.map((v) => ({ id: v }))),
|
body: JSON.stringify(roles),
|
||||||
},
|
},
|
||||||
).catch((e) => console.log(e));
|
).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
|
@ -165,7 +261,7 @@ export async function addUserRoles(userId: string, roleId: string[]) {
|
||||||
*
|
*
|
||||||
* @returns true if success, false otherwise.
|
* @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(
|
const res = await fetch(
|
||||||
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
`${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`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
body: JSON.stringify(roleId.map((v) => ({ id: v }))),
|
body: JSON.stringify(roles),
|
||||||
},
|
},
|
||||||
).catch((e) => console.log(e));
|
).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
|
|
||||||
22
tsoa.json
22
tsoa.json
|
|
@ -12,6 +12,28 @@
|
||||||
"description": "Keycloak Bearer Token",
|
"description": "Keycloak Bearer Token",
|
||||||
"in": "header"
|
"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": {
|
"routes": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue