Compare commits
76 Commits
fix/calcul
...
dev
Author | SHA1 | Date | |
---|---|---|---|
123be7da57 | |||
30075d4043 | |||
534339533f | |||
6701653964 | |||
19ce38986c | |||
680db6bbec | |||
8ae0a721fb | |||
ff68956fb5 | |||
5070cc9f50 | |||
302802946c | |||
bb81299c25 | |||
27d09e02a7 | |||
e49ec0e1cc | |||
9de4b4d894 | |||
91b67483b8 | |||
1324c917ce | |||
c3d9e2a0d3 | |||
a1ec819de7 | |||
c8f2b77b93 | |||
0ac712e69d | |||
9b416ec3f3 | |||
b589f416e8 | |||
e2b4a699b2 | |||
1df10f9e33 | |||
b3a689e447 | |||
70fd6535f7 | |||
bd2e1372f4 | |||
71fd522c46 | |||
dc25cc41df | |||
a2baad87d7 | |||
d80378df68 | |||
b23e8876a0 | |||
846282e1c6 | |||
148e8de6be | |||
481fe2bcfc | |||
c60c3b1125 | |||
72709b90c8 | |||
b2eec34ed0 | |||
41e27e28ff | |||
0971ac8ef6 | |||
d738c3f594 | |||
d711c20db0 | |||
8250fbe713 | |||
397e72170c | |||
acfb364a67 | |||
adcb57f157 | |||
4355698db8 | |||
e08c9246e2 | |||
6875e4d4e4 | |||
4d52ea8364 | |||
ee00fc3ac7 | |||
5c94e5264f | |||
3c7a95705e | |||
![]() |
98b7451157 | ||
928b368b8b | |||
![]() |
c25f72a185 | ||
62d3038a1e | |||
b30590dbfb | |||
c7cc06aa61 | |||
![]() |
1990809cec | ||
![]() |
4f34378010 | ||
![]() |
651f99d2c5 | ||
![]() |
c3fb5177bc | ||
08e15f5108 | |||
6be82fa7d5 | |||
c5a6704961 | |||
f1e30336dc | |||
65a694f351 | |||
0ec6a755ab | |||
08f08b8779 | |||
c694f5fe1c | |||
4adccd374b | |||
013fe877f2 | |||
5b1d664649 | |||
24dda5ecd4 | |||
93bbcc3251 |
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["@commitlint/config-conventional"],
|
|
||||||
"rules": {
|
|
||||||
"type-enum": [2, "always", ["feat", "fix", "docs", "style", "refactor", "revert", "chore", "build", "ci", "test"]]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
jest.config.js
|
|
145
.eslintrc
145
.eslintrc
@ -1,145 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2022": true,
|
|
||||||
"jest": true
|
|
||||||
},
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["@typescript-eslint", "prettier", "import"],
|
|
||||||
"extends": [
|
|
||||||
"prettier",
|
|
||||||
"eslint:recommended",
|
|
||||||
"eslint-config-prettier",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/eslint-recommended"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"import/resolver": {
|
|
||||||
"node": {
|
|
||||||
"extensions": [".js", ".ts"],
|
|
||||||
"paths": ["./src"]
|
|
||||||
},
|
|
||||||
"typescript": {
|
|
||||||
"alwaysTryTypes": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"import/parsers": {
|
|
||||||
"@typescript-eslint/parser": [".ts"]
|
|
||||||
},
|
|
||||||
"extensions": [".js", ".ts"],
|
|
||||||
"import/ignore": ["node_modules"]
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2022,
|
|
||||||
"sourceType": "module",
|
|
||||||
"project": ["./tsconfig.json"],
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"jsx": false,
|
|
||||||
"arrowFunctions": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
|
||||||
"@typescript-eslint/no-var-requires": "error",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/quotes": ["error", "double"],
|
|
||||||
"@typescript-eslint/no-use-before-define": "off",
|
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
|
||||||
"@typescript-eslint/ban-ts-comment": 0,
|
|
||||||
"@typescript-eslint/no-shadow": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
|
||||||
"@typescript-eslint/lines-between-class-members": ["error", { "exceptAfterOverload": true }],
|
|
||||||
"@typescript-eslint/naming-convention": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"selector": "default",
|
|
||||||
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "variable",
|
|
||||||
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"selector": "typeLike",
|
|
||||||
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "enumMember",
|
|
||||||
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "objectLiteralProperty",
|
|
||||||
"format": [],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"import/no-named-default": 0,
|
|
||||||
"import/prefer-default-export": "off",
|
|
||||||
"import/no-dynamic-require": "off",
|
|
||||||
"import/no-duplicates": "off",
|
|
||||||
"import/no-unresolved": "error",
|
|
||||||
"import/no-cycle": "off",
|
|
||||||
"import/extensions": [
|
|
||||||
"error",
|
|
||||||
"ignorePackages",
|
|
||||||
{
|
|
||||||
"js": "never",
|
|
||||||
"jsx": "never",
|
|
||||||
"ts": "never",
|
|
||||||
"tsx": "never"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
|
||||||
"import/order": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"newlines-between": "always-and-inside-groups",
|
|
||||||
"groups": [["builtin", "external"], ["internal", "sibling"], "type"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"prettier/prettier": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"endOfLine": "auto"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"comma-dangle": "off",
|
|
||||||
"class-methods-use-this": "off",
|
|
||||||
"func-names": "off",
|
|
||||||
"function-paren-newline": "off",
|
|
||||||
"global-require": "off",
|
|
||||||
"implicit-arrow-linebreak": 0,
|
|
||||||
"no-use-before-define": "off",
|
|
||||||
"no-inner-declarations": "off",
|
|
||||||
"no-console": ["warn", { "allow": ["error", "info"] }],
|
|
||||||
"consistent-return": "off",
|
|
||||||
"no-alert": "error",
|
|
||||||
"no-process-exit": "error",
|
|
||||||
"no-shadow": "error",
|
|
||||||
"no-param-reassign": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"props": true,
|
|
||||||
"ignorePropertyModificationsFor": ["accamulator", "request"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object-shorthand": "off",
|
|
||||||
"quotes": ["error", "double"]
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"window": false,
|
|
||||||
"FormData": false,
|
|
||||||
"Blob": true,
|
|
||||||
"document": false
|
|
||||||
}
|
|
||||||
}
|
|
24
.gitea/workflows/deploy.yml
Normal file
24
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: Deploy
|
||||||
|
run-name: ${{ gitea.actor }} build image and push to container registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
- 'staging'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CreateImage:
|
||||||
|
runs-on: [hubstaging]
|
||||||
|
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p
|
||||||
|
with:
|
||||||
|
runner: hubstaging
|
||||||
|
secrets:
|
||||||
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||||
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
DeployService:
|
||||||
|
runs-on: [hubstaging]
|
||||||
|
needs: CreateImage
|
||||||
|
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.4-p7
|
||||||
|
with:
|
||||||
|
runner: hubstaging
|
14
.gitea/workflows/lint.yml
Normal file
14
.gitea/workflows/lint.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
name: Lint
|
||||||
|
run-name: ${{ gitea.actor }} produce linting
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Lint:
|
||||||
|
runs-on: [hubstaging]
|
||||||
|
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/lint.yml@v1.1.2
|
||||||
|
with:
|
||||||
|
runner: hubstaging
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
nohup.out
|
nohup.out
|
||||||
.env
|
.env
|
||||||
|
main
|
||||||
/.idea
|
/.idea
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
include:
|
|
||||||
- project: "devops/pena-continuous-integration"
|
|
||||||
file: "/nodejs/docker/nodejs.gitlab-ci.yml"
|
|
@ -1,4 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx --no -- commitlint --edit ${1}
|
|
@ -1,4 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
yarn run code:format && yarn run lint && yarn run code:check && git add .
|
|
12
.prettierrc
12
.prettierrc
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": true,
|
|
||||||
"trailingComma": "es5",
|
|
||||||
"singleQuote": false,
|
|
||||||
"printWidth": 120,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"endOfLine": "auto",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"arrowParens": "always",
|
|
||||||
"jsxSingleQuote": false
|
|
||||||
}
|
|
44
Dockerfile
44
Dockerfile
@ -1,37 +1,13 @@
|
|||||||
FROM node:19.1-alpine AS dev
|
FROM gitea.pena/penadevops/container-images/golang:main as build
|
||||||
|
|
||||||
RUN apk update && rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
WORKDIR /usr/app
|
|
||||||
|
|
||||||
# Install node dependencies - done in a separate step so Docker can cache it.
|
|
||||||
COPY yarn.lock .
|
|
||||||
COPY package.json .
|
|
||||||
COPY tsconfig.json .
|
|
||||||
|
|
||||||
RUN yarn install --ignore-scripts --non-interactive --frozen-lockfile && yarn cache clean
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN ls
|
|
||||||
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
FROM node:19.1-alpine AS production
|
|
||||||
|
|
||||||
RUN apk update && rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
ENV GOPRIVATE=gitea.pena/PenaSide/common,gitea.pena/PenaSide/linters-golang,gitea.pena/PenaSide/customer,gitea.pena/PenaSide/trashlog,gitea.pena/PenaSide/hlog
|
||||||
|
ENV GOINSECURE=gitea.pena/PenaSide/common,gitea.pena/PenaSide/linters-golang,gitea.pena/PenaSide/customer,gitea.pena/PenaSide/trashlog,gitea.pena/PenaSide/hlog
|
||||||
|
COPY . /app
|
||||||
|
RUN go mod download
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app ./cmd/main.go
|
||||||
|
|
||||||
COPY --from=dev /usr/app/dist /app
|
FROM gitea.pena/penadevops/container-images/alpine:main
|
||||||
COPY --from=dev /usr/app/package.json /app/
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY --from=dev /usr/app/yarn.lock /app/
|
COPY --from=build app/app .
|
||||||
COPY --from=dev /usr/app/tsconfig.json .
|
CMD ["/app"]
|
||||||
|
|
||||||
RUN chown -R node: .
|
|
||||||
|
|
||||||
USER node
|
|
||||||
|
|
||||||
RUN yarn install --non-interactive --frozen-lockfile --production && yarn cache clean
|
|
||||||
|
|
||||||
CMD ["yarn", "start:prod"]
|
|
||||||
|
10
Taskfile.dist.yml
Normal file
10
Taskfile.dist.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
update-linter:
|
||||||
|
cmds:
|
||||||
|
- go get -u gitea.pena/PenaSide/linters-golang
|
||||||
|
lint:
|
||||||
|
cmds:
|
||||||
|
- task: update-linter
|
||||||
|
- cmd: golangci-lint run -v -c $(go list -f '{{"{{"}}.Dir{{"}}"}}' -m gitea.pena/PenaSide/linters-golang)/.golangci.yml
|
33
cmd/main.go
Normal file
33
cmd/main.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/app"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/initialize"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
_ "gitea.pena/PenaSide/linters-golang/pkg/dummy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := zap.NewProduction()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to initialize logger: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := initialize.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Failed to load config", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
if err = app.Run(ctx, *config, logger); err != nil {
|
||||||
|
logger.Fatal("App exited with error", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
24
deployments/main/docker-compose.yaml
Normal file
24
deployments/main/docker-compose.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
version: "3.3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
admin:
|
||||||
|
container_name: hub-admin-backend-service
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
image: $CI_REGISTRY_IMAGE/main:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
|
ports:
|
||||||
|
- 10.8.0.8:59303:8005
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
environment:
|
||||||
|
- DB_HOST=10.8.0.8
|
||||||
|
- DB_PORT=27017
|
||||||
|
- ENVIRONMENT=staging
|
||||||
|
- HTTP_HOST=0.0.0.0
|
||||||
|
- HTTP_PORT=8005
|
||||||
|
- AUTH_SERVICE_HOST=http://10.8.0.8
|
||||||
|
- AUTH_SERVICE_PORT=59300
|
||||||
|
- DB_USERNAME=$DB_USERNAME-prod
|
||||||
|
- DB_PASSWORD=$DB_PASSWORD
|
||||||
|
- DB_NAME=administrator
|
||||||
|
- PUBLIC_ACCESS_SECRET_KEY=$PUBLIC_ACCESS_SECRET_KEY
|
@ -1,28 +1,22 @@
|
|||||||
version: "3.3"
|
version: "3.3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
admin:
|
tariffs:
|
||||||
container_name: hub-admin-backend-service
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
tty: true
|
tty: true
|
||||||
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
image: gitea.pena:3000/penaside/tariffs/staging:$GITHUB_RUN_NUMBER
|
||||||
expose:
|
ports:
|
||||||
- 8005
|
- 10.7.0.6:59303:8005
|
||||||
networks:
|
- 10.7.0.6:59304:8006
|
||||||
- marketplace_penahub_frontend
|
|
||||||
- default
|
|
||||||
environment:
|
environment:
|
||||||
- DB_HOST=10.6.0.11
|
MONGO_URL: mongodb://administrator:64143ffdd9304865586e5cf1@mongodb.pena:27017/administrator?authSource=administrator
|
||||||
- DB_PORT=27017
|
MONGO_DB_NAME: administrator
|
||||||
- ENVIRONMENT=staging
|
DB_HOST: 10.7.0.6
|
||||||
- HTTP_HOST=0.0.0.0
|
DB_PORT: 27017
|
||||||
- HTTP_PORT=8005
|
ENVIRONMENT: staging
|
||||||
- AUTH_SERVICE_HOST=http://pena-auth-service
|
INTERNAL_HTTP_ADDRESS: 0.0.0.0:8006
|
||||||
- AUTH_SERVICE_PORT=8080
|
EXTERNAL_HTTP_ADDRESS: 0.0.0.0:8005
|
||||||
- DB_USERNAME=$DB_USERNAME
|
AUTH_SERVICE_HOST: http://10.7.0.4
|
||||||
- DB_PASSWORD=$DB_PASSWORD
|
AUTH_SERVICE_PORT: 59300
|
||||||
- DB_NAME=administrator
|
JWT_PUBLIC_KEY: "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLW1tlHyKC9AG0hGpmkksET2DE\nr7ojSPemxFWAgFgcPJWQ7x3uNbsdJ3bIZFoA/FClaWKMCZmjnH9tv0bKZtY/CDhM\nZEyHpMruRSn6IKrxjtQZWy4uv/w6MzUeyBYG0OvNCiYpdvz5SkAGAUHD5ZNFqn2w\nKKFD0I2Dr59BFVSGJwIDAQAB\n-----END PUBLIC KEY-----"
|
||||||
- PUBLIC_ACCESS_SECRET_KEY=$PUBLIC_ACCESS_SECRET_KEY
|
PUBLIC_ACCESS_SECRET_KEY: "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLW1tlHyKC9AG0hGpmkksET2DE\nr7ojSPemxFWAgFgcPJWQ7x3uNbsdJ3bIZFoA/FClaWKMCZmjnH9tv0bKZtY/CDhM\nZEyHpMruRSn6IKrxjtQZWy4uv/w6MzUeyBYG0OvNCiYpdvz5SkAGAUHD5ZNFqn2w\nKKFD0I2Dr59BFVSGJwIDAQAB\n-----END PUBLIC KEY-----"
|
||||||
networks:
|
|
||||||
marketplace_penahub_frontend:
|
|
||||||
external: true
|
|
||||||
|
8
deployments/test/.env.test
Normal file
8
deployments/test/.env.test
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
PUBLIC_ACCESS_SECRET_KEY="-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm69
|
||||||
|
80fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6B
|
||||||
|
dA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y
|
||||||
|
+3GyaOY536H47qyXAgMBAAE=
|
||||||
|
-----END PUBLIC KEY-----"
|
||||||
|
|
||||||
|
SALT=10
|
61
deployments/test/docker-compose.yaml
Normal file
61
deployments/test/docker-compose.yaml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
version: "3.3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
hub-admin-backend-service:
|
||||||
|
container_name: hub-admin-backend-service
|
||||||
|
tty: true
|
||||||
|
build:
|
||||||
|
context: ../../.
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: test
|
||||||
|
env_file:
|
||||||
|
- .env.test
|
||||||
|
environment:
|
||||||
|
- HTTP_HOST=0.0.0.0
|
||||||
|
- HTTP_PORT=8000
|
||||||
|
- DB_HOST=hub-admin-backend-db
|
||||||
|
- DB_PORT=27017
|
||||||
|
- DB_USERNAME=test
|
||||||
|
- DB_PASSWORD=test
|
||||||
|
- DB_NAME=admin
|
||||||
|
- ENVIRONMENT=staging
|
||||||
|
- AUTH_SERVICE_HOST=http://pena-auth-service
|
||||||
|
- AUTH_SERVICE_PORT=8000
|
||||||
|
depends_on:
|
||||||
|
- hub-admin-backend-migration
|
||||||
|
- hub-admin-backend-db
|
||||||
|
ports:
|
||||||
|
- 8001:8000
|
||||||
|
networks:
|
||||||
|
- test
|
||||||
|
|
||||||
|
hub-admin-backend-migration:
|
||||||
|
container_name: hub-admin-backend-migration
|
||||||
|
build:
|
||||||
|
context: ../../.
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: test
|
||||||
|
command:
|
||||||
|
[
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
'migrate -source file://migrations -database "mongodb://test:test@hub-admin-backend-db:27017/admin?authSource=admin" up',
|
||||||
|
]
|
||||||
|
depends_on:
|
||||||
|
- hub-admin-backend-db
|
||||||
|
networks:
|
||||||
|
- test
|
||||||
|
|
||||||
|
hub-admin-backend-db:
|
||||||
|
container_name: hub-admin-backend-db
|
||||||
|
image: "mongo:6.0.3"
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: test
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: test
|
||||||
|
ports:
|
||||||
|
- "27021:27017"
|
||||||
|
networks:
|
||||||
|
- test
|
||||||
|
|
||||||
|
networks:
|
||||||
|
test:
|
641
docs/openapi.yaml
Normal file
641
docs/openapi.yaml
Normal file
@ -0,0 +1,641 @@
|
|||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: hub_admin_backend_service
|
||||||
|
description: hub_admin_backend_service
|
||||||
|
version: 1.0.0
|
||||||
|
tags:
|
||||||
|
- name : PrivilegeExternal
|
||||||
|
description: Внешний сервер
|
||||||
|
- name : PrivilegeInternal
|
||||||
|
description: Внутренний сервер
|
||||||
|
- name: TariffExternal
|
||||||
|
description: Внешний сервер
|
||||||
|
- name: TariffInternal
|
||||||
|
description: Внутренний сервер
|
||||||
|
paths:
|
||||||
|
/privilege/service:
|
||||||
|
get:
|
||||||
|
summary: Получить все привилегии поделенные по сервисам в ассоциативном массиве
|
||||||
|
tags:
|
||||||
|
- PrivilegeExternal
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Успех. Получен ассоциативный массив, ключ - serviceKey, значение - массив привилегий этого сервиса.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ServicePrivilegesMap"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
/privilege:
|
||||||
|
get:
|
||||||
|
summary: Получить все привилегии
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Успех. Возвращается список привилегий.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
post:
|
||||||
|
summary: Создать новую привилегию
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
requestBody:
|
||||||
|
description: Данные для создания привилегии
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CreateUpdateReq"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Привилегия успешно создана
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'409':
|
||||||
|
$ref: "#/components/responses/409"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
put:
|
||||||
|
summary: Обновить существующую привилегию
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
requestBody:
|
||||||
|
description: Данные для обновления привилегии
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CreateUpdateReq"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Привилегия успешно обновлена
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
delete:
|
||||||
|
summary: Удалить привилегию
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
requestBody:
|
||||||
|
description: Данные для удвления привилегии
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/DeleteRestoreReq"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Привилегия успешно удалена
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
/privilege/{privilegeId}:
|
||||||
|
get:
|
||||||
|
summary: Получить привилегию по ID
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
parameters:
|
||||||
|
- name: privilegeId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Идентификатор привилегии
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Привилегия успешно получена
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
/privilege/service/{serviceKey}:
|
||||||
|
get:
|
||||||
|
summary: Получить привилегии по ключу сервиса
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
parameters:
|
||||||
|
- name: serviceKey
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Ключ сервиса
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Успешный ответ
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
/privilege/many:
|
||||||
|
post:
|
||||||
|
summary: Создать несколько привилегий
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
requestBody:
|
||||||
|
description: Данные для создания привилегий
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostPutMany"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Успешный ответ
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'409':
|
||||||
|
$ref: "#/components/responses/409"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
put:
|
||||||
|
summary: Обновить несколько привилегий
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
requestBody:
|
||||||
|
description: Данные для обновления привилегий
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostPutMany"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Успешный ответ
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
/privilege/restore:
|
||||||
|
post:
|
||||||
|
summary: Восстановить удалённые привилегии
|
||||||
|
tags:
|
||||||
|
- PrivilegeInternal
|
||||||
|
requestBody:
|
||||||
|
description: Данные для восстановления привилегии
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/DeleteRestoreReq"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Привилегия успешно восстановлена
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
/tariff/{id}:
|
||||||
|
get:
|
||||||
|
summary: получить тарифф по id
|
||||||
|
tags:
|
||||||
|
- TariffExternal
|
||||||
|
- TariffInternal
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Тарифф успешно получен
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Tariff"
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
put:
|
||||||
|
summary: обновить тарифф по id
|
||||||
|
tags:
|
||||||
|
- TariffInternal
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: id тарифа для обновления
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tariff'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Тариф успешно обновлен
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tariff'
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
/tariff/getList:
|
||||||
|
get:
|
||||||
|
summary: получить тариффы с пагинацией
|
||||||
|
tags:
|
||||||
|
- TariffExternal
|
||||||
|
- TariffInternal
|
||||||
|
parameters:
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
description: Номер страницы для пагинации
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 1
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Количество элементов на странице
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 25
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Успешный ответ с тариффами
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TariffPagination'
|
||||||
|
'401':
|
||||||
|
$ref: "#/components/responses/401"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
/tariff/:
|
||||||
|
post:
|
||||||
|
summary: создать тарифф
|
||||||
|
tags:
|
||||||
|
- TariffExternal
|
||||||
|
- TariffInternal
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tariff'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Тариф успешно создан
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tariff'
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'401':
|
||||||
|
$ref: "#/components/responses/401"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
delete:
|
||||||
|
summary: метод мягкого удаления тариффа
|
||||||
|
tags:
|
||||||
|
- TariffInternal
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: id тарифа для удаления
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Тариф успешно удален
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tariff'
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
/tariff/restore:
|
||||||
|
post:
|
||||||
|
summary: метод восстановления тариффа из мягко удаленных
|
||||||
|
tags:
|
||||||
|
- TariffInternal
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: id тарифа для восстановления
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Тариф успешно восстановлен
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tariff'
|
||||||
|
'400':
|
||||||
|
$ref: "#/components/responses/400"
|
||||||
|
'404':
|
||||||
|
$ref: "#/components/responses/404"
|
||||||
|
'500':
|
||||||
|
$ref: "#/components/responses/500"
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Privilege:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: Уникальный идентификатор привилегии
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Имя привилегии
|
||||||
|
privilegeId:
|
||||||
|
type: string
|
||||||
|
description: Идентификатор привилегии
|
||||||
|
serviceKey:
|
||||||
|
type: string
|
||||||
|
description: Ключ сервиса, к которому относится привилегия
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Описание привилегии
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: Тип привилегии
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
description: Значение привилегии
|
||||||
|
price:
|
||||||
|
type: number
|
||||||
|
format: float
|
||||||
|
description: Цена привилегии
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Дата создания привилегии
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Дата последнего обновления привилегии
|
||||||
|
isDeleted:
|
||||||
|
type: boolean
|
||||||
|
description: Флаг удаления привилегии
|
||||||
|
deletedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Дата удаления привилегии (если была удалена)
|
||||||
|
nullable: true
|
||||||
|
ServicePrivilegesMap:
|
||||||
|
type: object
|
||||||
|
description: Ассоциативный массив, где ключ - serviceKey, значение - массив привилегий этого сервиса.
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Privilege"
|
||||||
|
CreateUpdateReq:
|
||||||
|
type: object
|
||||||
|
description: Данные для создания или обновления привилегии
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Имя привилегии
|
||||||
|
privilegeId:
|
||||||
|
type: string
|
||||||
|
description: Уникальный идентификатор привилегии
|
||||||
|
serviceKey:
|
||||||
|
type: string
|
||||||
|
description: Ключ сервиса, к которому относится привилегия
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Описание привилегии
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: Тип привилегии
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
description: Значение привилегии
|
||||||
|
price:
|
||||||
|
type: number
|
||||||
|
format: float
|
||||||
|
description: Цена привилегии
|
||||||
|
DeleteRestoreReq:
|
||||||
|
type: object
|
||||||
|
description: Данные для удаления, восстановления привилегии
|
||||||
|
properties:
|
||||||
|
privilegeId:
|
||||||
|
type: string
|
||||||
|
description: Идентификатор привилегии, которую нужно удалить
|
||||||
|
PostPutMany:
|
||||||
|
type: object
|
||||||
|
description: Данные для создания, обновления массива привилегий
|
||||||
|
properties:
|
||||||
|
privileges:
|
||||||
|
type: array
|
||||||
|
description: Массив объектов для создания или обновления привилегий
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/CreateUpdateReq"
|
||||||
|
Tariff:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
_id:
|
||||||
|
type: string
|
||||||
|
format: objectId
|
||||||
|
description: id тарифа objectID в mongo
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Название тариффа
|
||||||
|
userID:
|
||||||
|
type: string
|
||||||
|
description: id пользователя котторому принадлежит тариф, либо пустое если тарифф создаваля админом
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Описание тариффа
|
||||||
|
price:
|
||||||
|
type: integer
|
||||||
|
description: цена тарифа
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
description:
|
||||||
|
isCustom:
|
||||||
|
type: boolean
|
||||||
|
description: Флаг состояни кастомного тариффа
|
||||||
|
privileges:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Privilege'
|
||||||
|
description: Привилегии входящие в тарифф
|
||||||
|
isDeleted:
|
||||||
|
type: boolean
|
||||||
|
description: Флаг состояния удаления
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Время создания
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Последнее время удаления
|
||||||
|
deletedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Время удаления
|
||||||
|
TariffPagination:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
totalPages:
|
||||||
|
type: integer
|
||||||
|
description: количество страниц на количесво элементов
|
||||||
|
tariffs:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Tariff'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Success
|
||||||
|
'201':
|
||||||
|
description: Created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Created
|
||||||
|
'204':
|
||||||
|
description: No content
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: No content
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Bad Request
|
||||||
|
'401':
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Unauthorized
|
||||||
|
'403':
|
||||||
|
description: Forbidden
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Forbidden
|
||||||
|
'404':
|
||||||
|
description: Not Found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Not Found
|
||||||
|
'409':
|
||||||
|
description: Conflict
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Already exist
|
||||||
|
'500':
|
||||||
|
description: Internal Server Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Internal Server Error
|
40
go.mod
Normal file
40
go.mod
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
module gitea.pena/PenaSide/tariffs
|
||||||
|
|
||||||
|
go 1.23.2
|
||||||
|
|
||||||
|
toolchain go1.23.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
gitea.pena/PenaSide/common v0.0.0-20241231090536-2454377ad2a0
|
||||||
|
github.com/caarlos0/env/v8 v8.0.0
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
go.mongodb.org/mongo-driver v1.15.0
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
|
golang.org/x/text v0.19.0 // indirect
|
||||||
|
)
|
99
go.sum
Normal file
99
go.sum
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
gitea.pena/PenaSide/common v0.0.0-20241231090536-2454377ad2a0 h1:B4+DAND6gJnCsc9DZY6XyMVLzD23nI8whk8xI7XKGrM=
|
||||||
|
gitea.pena/PenaSide/common v0.0.0-20241231090536-2454377ad2a0/go.mod h1:U7QFuvkrIWyb/m/SOyrsroS7DJntjcr9k7kNy3vtPdU=
|
||||||
|
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735 h1:jDVeUhGBTXBibmW5dmtJg2m2+z5z2Rf6J4G0LpjVoJ0=
|
||||||
|
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735/go.mod h1:gdd+vOT6up9STkEbxa2qESLIMZFjCmRbkcheFQCVgZU=
|
||||||
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
|
||||||
|
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
|
||||||
|
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
105
internal/app/app.go
Normal file
105
internal/app/app.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/middleware"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/initialize"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/server/http"
|
||||||
|
"gitea.pena/PenaSide/tariffs/pkg/closer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Error("Recovered from a panic", zap.Any("error", r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger.Info("Starting application", zap.Any("AppCFG", cfg))
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
shutdownGroup := closer.NewCloserGroup()
|
||||||
|
|
||||||
|
mdb, err := initialize.MongoDB(ctx, cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to initialize MongoDB", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mw := middleware.NewMiddleware(logger)
|
||||||
|
|
||||||
|
repositories := initialize.NewRepository(initialize.RepositoryDeps{
|
||||||
|
Logger: logger,
|
||||||
|
Mdb: mdb,
|
||||||
|
})
|
||||||
|
|
||||||
|
controllers := initialize.NewControllers(initialize.ControllerDeps{
|
||||||
|
Logger: logger,
|
||||||
|
Repos: repositories,
|
||||||
|
MW: mw,
|
||||||
|
})
|
||||||
|
|
||||||
|
internalSrv := http.NewServer(http.ServerConfig{
|
||||||
|
Logger: logger,
|
||||||
|
Controllers: []http.Controller{controllers.PrivilegeInternal, controllers.TariffInternal},
|
||||||
|
JWTConfig: &models.JWTConfiguration{
|
||||||
|
PrivateKey: cfg.PrivateKey,
|
||||||
|
PublicKey: cfg.PublicKey,
|
||||||
|
Issuer: cfg.Issuer,
|
||||||
|
Audience: cfg.Audience,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
externalSrv := http.NewServer(http.ServerConfig{
|
||||||
|
Logger: logger,
|
||||||
|
Controllers: []http.Controller{controllers.PrivilegeExternal, controllers.TariffExternal},
|
||||||
|
JWTConfig: &models.JWTConfiguration{
|
||||||
|
PrivateKey: cfg.PrivateKey,
|
||||||
|
PublicKey: cfg.PublicKey,
|
||||||
|
Issuer: cfg.Issuer,
|
||||||
|
Audience: cfg.Audience,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := internalSrv.Start(cfg.InternalHttpAddress); err != nil {
|
||||||
|
logger.Error("Server startup error", zap.Error(err))
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := externalSrv.Start(cfg.ExternalHttpAddress); err != nil {
|
||||||
|
logger.Error("Server startup error", zap.Error(err))
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
internalSrv.ListRoutes()
|
||||||
|
externalSrv.ListRoutes()
|
||||||
|
|
||||||
|
shutdownGroup.Add(closer.CloserFunc(internalSrv.Shutdown))
|
||||||
|
shutdownGroup.Add(closer.CloserFunc(externalSrv.Shutdown))
|
||||||
|
shutdownGroup.Add(closer.CloserFunc(mdb.Client().Disconnect))
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
|
||||||
|
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer timeoutCancel()
|
||||||
|
if err := shutdownGroup.Call(timeoutCtx); err != nil {
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
logger.Error("Shutdown timed out", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
logger.Error("Failed to shutdown services gracefully", zap.Error(err))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Application has stopped")
|
||||||
|
return nil
|
||||||
|
}
|
44
internal/controller/middleware/middleware.go
Normal file
44
internal/controller/middleware/middleware.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MiddleWare struct {
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMiddleware(logger *zap.Logger) *MiddleWare {
|
||||||
|
return &MiddleWare{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MiddleWare) ExtractUserID(ctx *fiber.Ctx) (string, bool) {
|
||||||
|
id, ok := ctx.Context().UserValue(models.AuthJWTDecodedUserIDKey).(string)
|
||||||
|
return id, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MiddleWare) ExtractToken(ctx *fiber.Ctx) (string, bool) {
|
||||||
|
token, ok := ctx.Context().UserValue(models.AuthJWTDecodedAccessTokenKey).(string)
|
||||||
|
return token, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MiddleWare) GetPaginationParameters(ctx *fiber.Ctx) (int64, int64) {
|
||||||
|
page := int64(1)
|
||||||
|
limit := int64(25)
|
||||||
|
if p := ctx.Query("page"); p != "" {
|
||||||
|
if parPage, err := strconv.ParseInt(p, 10, 64); err == nil {
|
||||||
|
page = parPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l := ctx.Query("limit"); l != "" {
|
||||||
|
if parLimit, err := strconv.ParseInt(l, 10, 64); err == nil {
|
||||||
|
limit = parLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return page, limit
|
||||||
|
}
|
41
internal/controller/privilege_external/controller.go
Normal file
41
internal/controller/privilege_external/controller.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package privilege_external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
our_errors "gitea.pena/PenaSide/tariffs/internal/errors"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/repository/privilege"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Repo *privilege.Privilege
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivilegeExternal struct {
|
||||||
|
repo *privilege.Privilege
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivilegeExternal(deps Deps) *PrivilegeExternal {
|
||||||
|
return &PrivilegeExternal{
|
||||||
|
repo: deps.Repo,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода getAllPrivilegesMap
|
||||||
|
func (p *PrivilegeExternal) GetAllService(c *fiber.Ctx) error {
|
||||||
|
privileges, err := p.repo.GetAllPrivileges(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Privileges not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusOK).JSON(tools.ConvertPrivilegesToMap(privileges))
|
||||||
|
}
|
13
internal/controller/privilege_external/route.go
Normal file
13
internal/controller/privilege_external/route.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package privilege_external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *PrivilegeExternal) Register(router fiber.Router) {
|
||||||
|
router.Get("/service", p.GetAllService)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrivilegeExternal) Name() string {
|
||||||
|
return "privilege"
|
||||||
|
}
|
244
internal/controller/privilege_internal/controller.go
Normal file
244
internal/controller/privilege_internal/controller.go
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
package privilege_internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
our_errors "gitea.pena/PenaSide/tariffs/internal/errors"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/repository/privilege"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Repo *privilege.Privilege
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivilegeInternal struct {
|
||||||
|
repo *privilege.Privilege
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivilegeInternal(deps Deps) *PrivilegeInternal {
|
||||||
|
return &PrivilegeInternal{
|
||||||
|
repo: deps.Repo,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода getAllPrivileges
|
||||||
|
func (p *PrivilegeInternal) Get(c *fiber.Ctx) error {
|
||||||
|
privileges, err := p.repo.GetAllPrivileges(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Privileges not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(privileges)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода registerPrivilege
|
||||||
|
func (p *PrivilegeInternal) Create(c *fiber.Ctx) error {
|
||||||
|
var req models.CreateUpdateReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tools.ValidatePrivilege(req) {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing required fields"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.repo.Create(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrAlreadyExist):
|
||||||
|
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Privilege already exist"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода replacePrivilege
|
||||||
|
func (p *PrivilegeInternal) Update(c *fiber.Ctx) error {
|
||||||
|
var req models.CreateUpdateReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tools.ValidatePrivilege(req) {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing required fields"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.repo.Update(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Privilege not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода removePrivilege
|
||||||
|
func (p *PrivilegeInternal) Delete(c *fiber.Ctx) error {
|
||||||
|
var req struct {
|
||||||
|
ID string `json:"privilegeId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PrivilegeID is required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.repo.Delete(c.Context(), req.ID)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Privilege not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода getPrivilege
|
||||||
|
func (p *PrivilegeInternal) GetByID(c *fiber.Ctx) error {
|
||||||
|
privilegeID := c.Params("privilegeId")
|
||||||
|
if privilegeID == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Privilege ID is required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.repo.GetByID(c.Context(), privilegeID)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Privilege not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода getServicePrivileges
|
||||||
|
func (p *PrivilegeInternal) GetByService(c *fiber.Ctx) error {
|
||||||
|
serviceKey := c.Params("serviceKey")
|
||||||
|
if serviceKey == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Service key is required"})
|
||||||
|
}
|
||||||
|
result, err := p.repo.GetByServiceKey(c.Context(), serviceKey)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Privileges not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода registerPrivileges
|
||||||
|
func (p *PrivilegeInternal) PostMany(c *fiber.Ctx) error {
|
||||||
|
var req models.ManyCreateUpdate
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Privileges) == 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "len array dont be 0"})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, priv := range req.Privileges {
|
||||||
|
if !tools.ValidatePrivilege(priv) {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing required fields"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.repo.PostMany(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrAlreadyExist):
|
||||||
|
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "One is privilege already exist"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода replacePrivileges
|
||||||
|
func (p *PrivilegeInternal) UpdateMany(c *fiber.Ctx) error {
|
||||||
|
var req models.ManyCreateUpdate
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Privileges) == 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "len array dont be 0"})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, priv := range req.Privileges {
|
||||||
|
if !tools.ValidatePrivilege(priv) {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing required fields"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.repo.UpdateMany(c.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "One is privilege not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// хаб нода restorePrivilege
|
||||||
|
func (p *PrivilegeInternal) Restore(c *fiber.Ctx) error {
|
||||||
|
var req struct {
|
||||||
|
ID string `json:"privilegeId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PrivilegeID is required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.repo.RestorePrivilege(c.Context(), req.ID)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Privilege not found"})
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
19
internal/controller/privilege_internal/route.go
Normal file
19
internal/controller/privilege_internal/route.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package privilege_internal
|
||||||
|
|
||||||
|
import "github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
func (p *PrivilegeInternal) Register(router fiber.Router) {
|
||||||
|
router.Get("/", p.Get)
|
||||||
|
router.Post("/", p.Create)
|
||||||
|
router.Put("/", p.Update)
|
||||||
|
router.Delete("/", p.Delete)
|
||||||
|
router.Get("/:privilegeId", p.GetByID)
|
||||||
|
router.Get("/service/:serviceKey", p.GetByService)
|
||||||
|
router.Post("/many", p.PostMany)
|
||||||
|
router.Put("/many", p.UpdateMany)
|
||||||
|
router.Post("/restore", p.Restore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrivilegeInternal) Name() string {
|
||||||
|
return "privilege"
|
||||||
|
}
|
98
internal/controller/tariff_external/controller.go
Normal file
98
internal/controller/tariff_external/controller.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package tariff_external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/middleware"
|
||||||
|
our_errors "gitea.pena/PenaSide/tariffs/internal/errors"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/repository/tariff"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Repo *tariff.Tariff
|
||||||
|
Logger *zap.Logger
|
||||||
|
MiddleWare *middleware.MiddleWare
|
||||||
|
}
|
||||||
|
|
||||||
|
type TariffExternal struct {
|
||||||
|
repo *tariff.Tariff
|
||||||
|
logger *zap.Logger
|
||||||
|
middleWare *middleware.MiddleWare
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTariffExternal(deps Deps) *TariffExternal {
|
||||||
|
return &TariffExternal{
|
||||||
|
repo: deps.Repo,
|
||||||
|
logger: deps.Logger,
|
||||||
|
middleWare: deps.MiddleWare,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffExternal) Get(ctx *fiber.Ctx) error {
|
||||||
|
tariffID := ctx.Params("id")
|
||||||
|
if tariffID == "" {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "url field id don't be empty"})
|
||||||
|
}
|
||||||
|
|
||||||
|
objID, err := primitive.ObjectIDFromHex(tariffID)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no valid object tariff id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.GetByID(ctx.Context(), objID)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tariff not found"})
|
||||||
|
default:
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffExternal) GetList(ctx *fiber.Ctx) error {
|
||||||
|
page, limit := t.middleWare.GetPaginationParameters(ctx)
|
||||||
|
|
||||||
|
userID, ok := t.middleWare.ExtractUserID(ctx)
|
||||||
|
if !ok {
|
||||||
|
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "No auth"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.GetList(ctx.Context(), page, limit, userID)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffExternal) Create(ctx *fiber.Ctx) error {
|
||||||
|
userID, ok := t.middleWare.ExtractUserID(ctx)
|
||||||
|
if !ok {
|
||||||
|
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "No auth"})
|
||||||
|
}
|
||||||
|
|
||||||
|
var req models.Tariff
|
||||||
|
if err := ctx.BodyParser(&req); err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
req.UserID = userID
|
||||||
|
|
||||||
|
err := tools.ValidateTariff(req)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.Create(ctx.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
13
internal/controller/tariff_external/route.go
Normal file
13
internal/controller/tariff_external/route.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package tariff_external
|
||||||
|
|
||||||
|
import "github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
func (t *TariffExternal) Register(router fiber.Router) {
|
||||||
|
router.Get("/getList", t.GetList)
|
||||||
|
router.Get("/:id", t.Get)
|
||||||
|
router.Post("/", t.Create)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffExternal) Name() string {
|
||||||
|
return "tariff"
|
||||||
|
}
|
179
internal/controller/tariff_internal/controller.go
Normal file
179
internal/controller/tariff_internal/controller.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package tariff_internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/middleware"
|
||||||
|
our_errors "gitea.pena/PenaSide/tariffs/internal/errors"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/repository/tariff"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo middleware jwt
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Repo *tariff.Tariff
|
||||||
|
Logger *zap.Logger
|
||||||
|
MiddleWare *middleware.MiddleWare
|
||||||
|
}
|
||||||
|
|
||||||
|
type TariffInternal struct {
|
||||||
|
repo *tariff.Tariff
|
||||||
|
logger *zap.Logger
|
||||||
|
middleWare *middleware.MiddleWare
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTariffInternal(deps Deps) *TariffInternal {
|
||||||
|
return &TariffInternal{
|
||||||
|
repo: deps.Repo,
|
||||||
|
logger: deps.Logger,
|
||||||
|
middleWare: deps.MiddleWare,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffInternal) Get(ctx *fiber.Ctx) error {
|
||||||
|
tariffID := ctx.Params("id")
|
||||||
|
if tariffID == "" {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "url field id don't be empty"})
|
||||||
|
}
|
||||||
|
|
||||||
|
objID, err := primitive.ObjectIDFromHex(tariffID)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no valid object tariff id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.GetByID(ctx.Context(), objID)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tariff not found"})
|
||||||
|
default:
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffInternal) GetList(ctx *fiber.Ctx) error {
|
||||||
|
page, limit := t.middleWare.GetPaginationParameters(ctx)
|
||||||
|
result, err := t.repo.GetList(ctx.Context(), page, limit, "")
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffInternal) Create(ctx *fiber.Ctx) error {
|
||||||
|
var req models.Tariff
|
||||||
|
if err := ctx.BodyParser(&req); err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tools.ValidateTariff(req)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.Create(ctx.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffInternal) Delete(ctx *fiber.Ctx) error {
|
||||||
|
var req struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.BodyParser(&req); err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == "" {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Tariff id don't be nil"})
|
||||||
|
}
|
||||||
|
|
||||||
|
objID, err := primitive.ObjectIDFromHex(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no valid object tariff id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.SoftDelete(ctx.Context(), objID)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tariff not found"})
|
||||||
|
default:
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffInternal) Update(ctx *fiber.Ctx) error {
|
||||||
|
var req models.Tariff
|
||||||
|
if err := ctx.BodyParser(&req); err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tools.ValidateTariff(req)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ctx.Params("id")
|
||||||
|
objID, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no valid object tariff id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.Update(ctx.Context(), objID, req)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tariff not found"})
|
||||||
|
default:
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffInternal) Restore(ctx *fiber.Ctx) error {
|
||||||
|
var req struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
if err := ctx.BodyParser(&req); err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == "" {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id don`t be empty"})
|
||||||
|
}
|
||||||
|
|
||||||
|
objID, err := primitive.ObjectIDFromHex(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no valid object tariff id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := t.repo.Restore(ctx.Context(), objID)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, our_errors.ErrNotFound):
|
||||||
|
return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tariff not found"})
|
||||||
|
default:
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||||
|
}
|
16
internal/controller/tariff_internal/route.go
Normal file
16
internal/controller/tariff_internal/route.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package tariff_internal
|
||||||
|
|
||||||
|
import "github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
func (t *TariffInternal) Register(router fiber.Router) {
|
||||||
|
router.Get("/:id", t.Get)
|
||||||
|
router.Get("/getList", t.GetList)
|
||||||
|
router.Post("/", t.Create)
|
||||||
|
router.Delete("/", t.Delete)
|
||||||
|
router.Put("/:id", t.Update)
|
||||||
|
router.Post("/restore", t.Restore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TariffInternal) Name() string {
|
||||||
|
return "tariff"
|
||||||
|
}
|
8
internal/errors/repository.go
Normal file
8
internal/errors/repository.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAlreadyExist = errors.New("already exist")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
34
internal/initialize/config.go
Normal file
34
internal/initialize/config.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.pena/PenaSide/common/mongo"
|
||||||
|
"github.com/caarlos0/env/v8"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AppName string `env:"APP_NAME" envDefault:"hub-admin-backend-service"`
|
||||||
|
InternalHttpAddress string `env:"INTERNAL_HTTP_ADDRESS" envDefault:"localhost:8000"`
|
||||||
|
ExternalHttpAddress string `env:"EXTERNAL_HTTP_ADDRESS" envDefault:"localhost:8001"`
|
||||||
|
PrivateKey string `env:"JWT_PRIVATE_KEY"`
|
||||||
|
PublicKey string `env:"JWT_PUBLIC_KEY"`
|
||||||
|
Issuer string `env:"JWT_ISSUER"`
|
||||||
|
Audience string `env:"JWT_AUDIENCE"`
|
||||||
|
ExternalCfg ExternalCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalCfg struct {
|
||||||
|
Database mongo.Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() (*Config, error) {
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
log.Print("No .env file found")
|
||||||
|
}
|
||||||
|
var config Config
|
||||||
|
if err := env.Parse(&config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
46
internal/initialize/controller.go
Normal file
46
internal/initialize/controller.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/middleware"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/privilege_external"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/privilege_internal"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/tariff_external"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/controller/tariff_internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ControllerDeps struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Repos *Repository
|
||||||
|
MW *middleware.MiddleWare
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
PrivilegeInternal *privilege_internal.PrivilegeInternal
|
||||||
|
PrivilegeExternal *privilege_external.PrivilegeExternal
|
||||||
|
TariffInternal *tariff_internal.TariffInternal
|
||||||
|
TariffExternal *tariff_external.TariffExternal
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewControllers(deps ControllerDeps) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
PrivilegeInternal: privilege_internal.NewPrivilegeInternal(privilege_internal.Deps{
|
||||||
|
Repo: deps.Repos.PrivilegeRepo,
|
||||||
|
Logger: deps.Logger,
|
||||||
|
}),
|
||||||
|
PrivilegeExternal: privilege_external.NewPrivilegeExternal(privilege_external.Deps{
|
||||||
|
Repo: deps.Repos.PrivilegeRepo,
|
||||||
|
Logger: deps.Logger,
|
||||||
|
}),
|
||||||
|
TariffInternal: tariff_internal.NewTariffInternal(tariff_internal.Deps{
|
||||||
|
Logger: deps.Logger,
|
||||||
|
Repo: deps.Repos.TariffRepo,
|
||||||
|
MiddleWare: deps.MW,
|
||||||
|
}),
|
||||||
|
TariffExternal: tariff_external.NewTariffExternal(tariff_external.Deps{
|
||||||
|
Logger: deps.Logger,
|
||||||
|
Repo: deps.Repos.TariffRepo,
|
||||||
|
MiddleWare: deps.MW,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
35
internal/initialize/mongo.go
Normal file
35
internal/initialize/mongo.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
mdb "gitea.pena/PenaSide/common/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) {
|
||||||
|
dbConfig := &mdb.Configuration{
|
||||||
|
URL: cfg.ExternalCfg.Database.URL,
|
||||||
|
DatabaseName: cfg.ExternalCfg.Database.DatabaseName,
|
||||||
|
}
|
||||||
|
|
||||||
|
newCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
mongoDeps := &mdb.ConnectDeps{
|
||||||
|
Configuration: dbConfig,
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := mdb.Connect(newCtx, mongoDeps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Client().Ping(newCtx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
32
internal/initialize/repository.go
Normal file
32
internal/initialize/repository.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/repository/privilege"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/repository/tariff"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepositoryDeps struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Mdb *mongo.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
PrivilegeRepo *privilege.Privilege
|
||||||
|
TariffRepo *tariff.Tariff
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepository(deps RepositoryDeps) *Repository {
|
||||||
|
return &Repository{
|
||||||
|
PrivilegeRepo: privilege.NewPrivilegeRepo(privilege.Deps{
|
||||||
|
Mdb: deps.Mdb.Collection("privileges"),
|
||||||
|
Logger: deps.Logger,
|
||||||
|
}),
|
||||||
|
TariffRepo: tariff.NewTariffRepo(tariff.Deps{
|
||||||
|
Mdb: deps.Mdb.Collection("tariffs"),
|
||||||
|
Logger: deps.Logger,
|
||||||
|
PrivilegeDB: deps.Mdb.Collection("privileges"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
18
internal/models/jwt.go
Normal file
18
internal/models/jwt.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JWTConfiguration struct {
|
||||||
|
PrivateKey string
|
||||||
|
PublicKey string
|
||||||
|
Issuer string
|
||||||
|
Audience string
|
||||||
|
Algorithm jwt.SigningMethodRSA
|
||||||
|
ExpiresIn time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthJWTDecodedUserIDKey = "userID"
|
||||||
|
const AuthJWTDecodedAccessTokenKey = "access-token"
|
24
internal/models/privilege.go
Normal file
24
internal/models/privilege.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type Privilege struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id" json:"_id"`
|
||||||
|
Name string `bson:"name" json:"name"`
|
||||||
|
PrivilegeID string `bson:"privilegeId" json:"privilegeId"`
|
||||||
|
ServiceKey string `bson:"serviceKey" json:"serviceKey"`
|
||||||
|
Description string `bson:"description" json:"description"`
|
||||||
|
Type string `bson:"type" json:"type"`
|
||||||
|
Value string `bson:"value" json:"value"`
|
||||||
|
Price float64 `bson:"price" json:"price"`
|
||||||
|
Amount float64 `bson:"amount" json:"amount"`
|
||||||
|
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||||
|
IsDeleted bool `bson:"isDeleted" json:"isDeleted"`
|
||||||
|
DeletedAt *time.Time `bson:"deletedAt" json:"deletedAt"`
|
||||||
|
}
|
||||||
|
|
20
internal/models/reqBodies.go
Normal file
20
internal/models/reqBodies.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type CreateUpdateReq struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
PrivilegeId string `json:"privilegeId"`
|
||||||
|
ServiceKey string `json:"serviceKey"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManyCreateUpdate struct {
|
||||||
|
Privileges []CreateUpdateReq `json:"privileges"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TariffPagination struct {
|
||||||
|
TotalPages int `json:"totalPages"`
|
||||||
|
Tariffs []Tariff `json:"tariffs"`
|
||||||
|
}
|
21
internal/models/tariff.go
Normal file
21
internal/models/tariff.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tariff struct {
|
||||||
|
ID primitive.ObjectID `json:"_id" bson:"_id"`
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
UserID string `json:"userID" bson:"userID"`
|
||||||
|
Description string `json:"description" bson:"description"`
|
||||||
|
Price int `json:"price" bson:"price"`
|
||||||
|
Order int `json:"order" bson:"order"`
|
||||||
|
IsCustom bool `json:"isCustom" bson:"isCustom"`
|
||||||
|
Privileges []Privilege `json:"privileges" bson:"privileges"`
|
||||||
|
IsDeleted bool `json:"isDeleted" bson:"isDeleted"`
|
||||||
|
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
||||||
|
DeletedAt time.Time `json:"deletedAt" bson:"deletedAt"`
|
||||||
|
}
|
297
internal/repository/privilege/privilege.go
Normal file
297
internal/repository/privilege/privilege.go
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
package privilege
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/errors"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Mdb *mongo.Collection
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type Privilege struct {
|
||||||
|
mdb *mongo.Collection
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivilegeRepo(deps Deps) *Privilege {
|
||||||
|
return &Privilege{
|
||||||
|
mdb: deps.Mdb,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) GetAllPrivileges(ctx context.Context) ([]models.Privilege, error) {
|
||||||
|
filter := bson.M{"isDeleted": false}
|
||||||
|
opts := options.Find()
|
||||||
|
|
||||||
|
cursor, err := p.mdb.Find(ctx, filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return nil, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
p.logger.Error("failed to find privileges", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
var privileges []models.Privilege
|
||||||
|
if err = cursor.All(ctx, &privileges); err != nil {
|
||||||
|
p.logger.Error("failed to decode privileges", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) Create(ctx context.Context, req models.CreateUpdateReq) (models.Privilege, error) {
|
||||||
|
exist := models.Privilege{}
|
||||||
|
err := p.mdb.FindOne(ctx, bson.M{"privilegeId": req.PrivilegeId}).Decode(&exist)
|
||||||
|
if err == nil {
|
||||||
|
return models.Privilege{}, errors.ErrAlreadyExist
|
||||||
|
} else if err != mongo.ErrNoDocuments {
|
||||||
|
p.logger.Error("Failed to check existing privilege", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privilege := models.Privilege{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: req.Name,
|
||||||
|
PrivilegeID: req.PrivilegeId,
|
||||||
|
ServiceKey: req.ServiceKey,
|
||||||
|
Description: req.Description,
|
||||||
|
Type: req.Type,
|
||||||
|
Value: req.Value,
|
||||||
|
Price: req.Price,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
IsDeleted: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.mdb.InsertOne(ctx, privilege)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Error("Failed to create privilege", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privilege, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) Update(ctx context.Context, req models.CreateUpdateReq) (models.Privilege, error) {
|
||||||
|
exist := models.Privilege{}
|
||||||
|
err := p.mdb.FindOne(ctx, bson.M{"privilegeId": req.PrivilegeId}).Decode(&exist)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return models.Privilege{}, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
p.logger.Error("Failed to find privilege", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
currTime := time.Now()
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"name": req.Name,
|
||||||
|
"serviceKey": req.ServiceKey,
|
||||||
|
"description": req.Description,
|
||||||
|
"type": req.Type,
|
||||||
|
"value": req.Value,
|
||||||
|
"price": req.Price,
|
||||||
|
"updatedAt": currTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.mdb.UpdateOne(ctx, bson.M{"privilegeId": req.PrivilegeId}, update)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Error("Failed to update privilege", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exist.Name = req.Name
|
||||||
|
exist.ServiceKey = req.ServiceKey
|
||||||
|
exist.Description = req.Description
|
||||||
|
exist.Type = req.Type
|
||||||
|
exist.Value = req.Value
|
||||||
|
exist.Price = req.Price
|
||||||
|
exist.UpdatedAt = currTime
|
||||||
|
|
||||||
|
return exist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) Delete(ctx context.Context, id string) (models.Privilege, error) {
|
||||||
|
var privilege models.Privilege
|
||||||
|
filter := bson.M{"privilegeId": id}
|
||||||
|
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"isDeleted": true,
|
||||||
|
"deletedAt": time.Now(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
|
||||||
|
err := p.mdb.FindOneAndUpdate(ctx, filter, update, opts).Decode(&privilege)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return models.Privilege{}, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
p.logger.Error("failed to delete privilege", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privilege, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) GetByID(ctx context.Context, id string) (models.Privilege, error) {
|
||||||
|
var privilege models.Privilege
|
||||||
|
filter := bson.M{
|
||||||
|
"privilegeId": id,
|
||||||
|
"isDeleted": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.mdb.FindOne(ctx, filter).Decode(&privilege)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return models.Privilege{}, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
p.logger.Error("failed to get privilege by privilege id", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privilege, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) GetByServiceKey(ctx context.Context, serviceKey string) ([]models.Privilege, error) {
|
||||||
|
filter := bson.M{
|
||||||
|
"serviceKey": serviceKey,
|
||||||
|
"isDeleted": false,
|
||||||
|
}
|
||||||
|
opts := options.Find()
|
||||||
|
|
||||||
|
cursor, err := p.mdb.Find(ctx, filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return nil, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
p.logger.Error("failed to find privileges by service key", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
var privileges []models.Privilege
|
||||||
|
if err = cursor.All(ctx, &privileges); err != nil {
|
||||||
|
p.logger.Error("failed to decode privileges", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) PostMany(ctx context.Context, req models.ManyCreateUpdate) ([]models.Privilege, error) {
|
||||||
|
var privileges []models.Privilege
|
||||||
|
for _, r := range req.Privileges {
|
||||||
|
exist := models.Privilege{}
|
||||||
|
err := p.mdb.FindOne(ctx, bson.M{"privilegeId": r.PrivilegeId}).Decode(&exist)
|
||||||
|
if err == nil {
|
||||||
|
p.logger.Error("privilege already exists", zap.String("privilegeId", r.PrivilegeId))
|
||||||
|
return nil, errors.ErrAlreadyExist
|
||||||
|
} else if err != mongo.ErrNoDocuments {
|
||||||
|
p.logger.Error("failed to check existing privilege", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privileges = append(privileges, models.Privilege{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: r.Name,
|
||||||
|
PrivilegeID: r.PrivilegeId,
|
||||||
|
ServiceKey: r.ServiceKey,
|
||||||
|
Description: r.Description,
|
||||||
|
Type: r.Type,
|
||||||
|
Value: r.Value,
|
||||||
|
Price: r.Price,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
IsDeleted: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
noneType := make([]interface{}, len(privileges))
|
||||||
|
for i, v := range privileges {
|
||||||
|
noneType[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := p.mdb.InsertMany(ctx, noneType)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Error("failed to insert privileges", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) UpdateMany(ctx context.Context, req models.ManyCreateUpdate) ([]models.Privilege, error) {
|
||||||
|
var updated []models.Privilege
|
||||||
|
for _, r := range req.Privileges {
|
||||||
|
filter := bson.M{"privilegeId": r.PrivilegeId}
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"name": r.Name,
|
||||||
|
"serviceKey": r.ServiceKey,
|
||||||
|
"description": r.Description,
|
||||||
|
"type": r.Type,
|
||||||
|
"value": r.Value,
|
||||||
|
"price": r.Price,
|
||||||
|
"updatedAt": time.Now(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedPrivilege models.Privilege
|
||||||
|
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
|
||||||
|
err := p.mdb.FindOneAndUpdate(ctx, filter, update, opts).Decode(&updatedPrivilege)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return nil, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
p.logger.Error("failed to update privilege", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated = append(updated, updatedPrivilege)
|
||||||
|
}
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Privilege) RestorePrivilege(ctx context.Context, id string) (models.Privilege, error) {
|
||||||
|
exist := models.Privilege{}
|
||||||
|
err := p.mdb.FindOne(ctx, bson.M{"privilegeId": id}).Decode(&exist)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return models.Privilege{}, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
p.logger.Error("Failed to find privilege", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
currTime := time.Now()
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"isDeleted": false,
|
||||||
|
"updatedAt": currTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.mdb.UpdateOne(ctx, bson.M{"privilegeId": id}, update)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Error("Failed to restore privilege", zap.Error(err))
|
||||||
|
return models.Privilege{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exist.IsDeleted = false
|
||||||
|
exist.UpdatedAt = currTime
|
||||||
|
|
||||||
|
return exist, nil
|
||||||
|
}
|
210
internal/repository/tariff/tariff.go
Normal file
210
internal/repository/tariff/tariff.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package tariff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/errors"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Mdb *mongo.Collection
|
||||||
|
Logger *zap.Logger
|
||||||
|
PrivilegeDB *mongo.Collection
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tariff struct {
|
||||||
|
mdb *mongo.Collection
|
||||||
|
logger *zap.Logger
|
||||||
|
privilegeDB *mongo.Collection
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTariffRepo(deps Deps) *Tariff {
|
||||||
|
return &Tariff{
|
||||||
|
mdb: deps.Mdb,
|
||||||
|
logger: deps.Logger,
|
||||||
|
privilegeDB: deps.PrivilegeDB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tariff) GetByID(ctx context.Context, id primitive.ObjectID) (models.Tariff, error) {
|
||||||
|
var tariff models.Tariff
|
||||||
|
filter := bson.M{
|
||||||
|
"_id": id,
|
||||||
|
"isDeleted": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := t.mdb.FindOne(ctx, filter).Decode(&tariff)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return tariff, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
t.logger.Error("failed to get tariff by object id", zap.Error(err))
|
||||||
|
return tariff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tariff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tariff) Create(ctx context.Context, req models.Tariff) (models.Tariff, error) {
|
||||||
|
req.ID = primitive.NewObjectID()
|
||||||
|
req.CreatedAt = time.Now()
|
||||||
|
req.IsDeleted = false
|
||||||
|
req.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
_, err := t.mdb.InsertOne(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.logger.Error("failed insert tariff", zap.Error(err))
|
||||||
|
return req, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tariff) GetList(ctx context.Context, page, limit int64, userID string) (models.TariffPagination, error) {
|
||||||
|
var result models.TariffPagination
|
||||||
|
filter := bson.M{
|
||||||
|
"$or": []bson.M{
|
||||||
|
{"isCustom": false, "isDeleted": false},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if userID != "" {
|
||||||
|
filter["$or"] = append(filter["$or"].([]bson.M), bson.M{"isCustom": true, "userID": userID})
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := t.mdb.CountDocuments(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
t.logger.Error("failed count tariffs", zap.Error(err))
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPages := int(math.Ceil(float64(count) / float64(limit)))
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
findOptions := options.Find().SetSort(bson.D{{"order", 1}}).SetSkip(offset).SetLimit(limit)
|
||||||
|
|
||||||
|
cursor, err := t.mdb.Find(ctx, filter, findOptions)
|
||||||
|
if err != nil {
|
||||||
|
t.logger.Error("failed find tariffs", zap.Error(err))
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
var tariffs []models.Tariff
|
||||||
|
if err = cursor.All(ctx, &tariffs); err != nil {
|
||||||
|
t.logger.Error("failed decode tariffs", zap.Error(err))
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TotalPages = totalPages
|
||||||
|
result.Tariffs = tariffs
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tariff) SoftDelete(ctx context.Context, tariffID primitive.ObjectID) (models.Tariff, error) {
|
||||||
|
var tariff models.Tariff
|
||||||
|
filter := bson.M{"_id": tariffID}
|
||||||
|
update := bson.M{"$set": bson.M{"isDeleted": true, "deletedAt": time.Now()}}
|
||||||
|
err := t.mdb.FindOneAndUpdate(ctx, filter, update, options.FindOneAndUpdate().SetReturnDocument(options.After)).Decode(&tariff)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return tariff, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
t.logger.Error("failed soft delete tariff", zap.Error(err))
|
||||||
|
return tariff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tariff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tariff) Update(ctx context.Context, tariffID primitive.ObjectID, req models.Tariff) (models.Tariff, error) {
|
||||||
|
var tariff models.Tariff
|
||||||
|
err := t.mdb.FindOne(ctx, bson.M{"_id": tariffID}).Decode(&tariff)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return tariff, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
t.logger.Error("failed find tariff", zap.Error(err))
|
||||||
|
return tariff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privilegeIDs := make([]string, len(req.Privileges))
|
||||||
|
for i, privilege := range req.Privileges {
|
||||||
|
privilegeIDs[i] = privilege.PrivilegeID
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := t.privilegeDB.Find(ctx, bson.M{"privilegeId": bson.M{"$in": privilegeIDs}})
|
||||||
|
if err != nil {
|
||||||
|
t.logger.Error("failed find privileges", zap.Error(err))
|
||||||
|
return tariff, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
privilegeMap := make(map[string]models.Privilege)
|
||||||
|
for cursor.Next(ctx) {
|
||||||
|
var privilege models.Privilege
|
||||||
|
if err := cursor.Decode(&privilege); err != nil {
|
||||||
|
t.logger.Error("failed decode privilege", zap.Error(err))
|
||||||
|
return tariff, err
|
||||||
|
}
|
||||||
|
privilegeMap[privilege.PrivilegeID] = privilege
|
||||||
|
}
|
||||||
|
|
||||||
|
clean := make([]models.Privilege, len(req.Privileges))
|
||||||
|
for i, privilege := range req.Privileges {
|
||||||
|
origPrivilege := privilegeMap[privilege.PrivilegeID]
|
||||||
|
clean[i] = models.Privilege{
|
||||||
|
Name: origPrivilege.Name,
|
||||||
|
PrivilegeID: origPrivilege.PrivilegeID,
|
||||||
|
ServiceKey: origPrivilege.ServiceKey,
|
||||||
|
Description: origPrivilege.Description,
|
||||||
|
Type: origPrivilege.Type,
|
||||||
|
Value: origPrivilege.Value,
|
||||||
|
Price: origPrivilege.Price,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"order": req.Order,
|
||||||
|
"name": req.Name,
|
||||||
|
"price": req.Price,
|
||||||
|
"isCustom": req.IsCustom,
|
||||||
|
"privileges": clean,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.mdb.FindOneAndUpdate(ctx, bson.M{"_id": tariffID}, update).Decode(&tariff)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return tariff, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
t.logger.Error("failed update tariff", zap.Error(err))
|
||||||
|
return tariff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tariff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tariff) Restore(ctx context.Context, tariffID primitive.ObjectID) (models.Tariff, error) {
|
||||||
|
var tariff models.Tariff
|
||||||
|
|
||||||
|
filter := bson.M{"_id": tariffID}
|
||||||
|
update := bson.M{"$set": bson.M{"isDeleted": false}}
|
||||||
|
err := t.mdb.FindOneAndUpdate(ctx, filter, update, options.FindOneAndUpdate().SetReturnDocument(options.After)).Decode(&tariff)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return tariff, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
t.logger.Error("failed restore tariff", zap.Error(err))
|
||||||
|
return tariff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tariff, nil
|
||||||
|
}
|
75
internal/server/http/http.go
Normal file
75
internal/server/http/http.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Controllers []Controller
|
||||||
|
JWTConfig *models.JWTConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Controllers []Controller
|
||||||
|
app *fiber.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(config ServerConfig) *Server {
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
jwtUtil := utils.NewJWT(config.JWTConfig)
|
||||||
|
app.Use("/tariff", utils.NewAuthenticator(jwtUtil))
|
||||||
|
app.Use("/privilege", func(c *fiber.Ctx) error {
|
||||||
|
return c.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Logger: config.Logger,
|
||||||
|
Controllers: config.Controllers,
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.registerRoutes()
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start(addr string) error {
|
||||||
|
if err := s.app.Listen(addr); err != nil {
|
||||||
|
s.Logger.Error("Failed to start server", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Shutdown(ctx context.Context) error {
|
||||||
|
return s.app.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) registerRoutes() {
|
||||||
|
for _, c := range s.Controllers {
|
||||||
|
router := s.app.Group(c.Name())
|
||||||
|
c.Register(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller interface {
|
||||||
|
Register(router fiber.Router)
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListRoutes() {
|
||||||
|
fmt.Println("Registered routes:")
|
||||||
|
for _, stack := range s.app.Stack() {
|
||||||
|
for _, route := range stack {
|
||||||
|
fmt.Printf("%s %s\n", route.Method, route.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
internal/tools/convertToMap.go
Normal file
17
internal/tools/convertToMap.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import "gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
|
||||||
|
func ConvertPrivilegesToMap(privileges []models.Privilege) map[string][]models.Privilege {
|
||||||
|
resultMap := make(map[string][]models.Privilege)
|
||||||
|
|
||||||
|
for _, privilege := range privileges {
|
||||||
|
svcKey := privilege.ServiceKey
|
||||||
|
if _, ok := resultMap[svcKey]; !ok {
|
||||||
|
resultMap[svcKey] = []models.Privilege{}
|
||||||
|
}
|
||||||
|
resultMap[svcKey] = append(resultMap[svcKey], privilege)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultMap
|
||||||
|
}
|
32
internal/tools/validate.go
Normal file
32
internal/tools/validate.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidatePrivilege(req models.CreateUpdateReq) bool {
|
||||||
|
if req.Name == "" || req.PrivilegeId == "" || req.ServiceKey == "" || req.Type == "" || req.Value == "" || req.Description == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateTariff(tariff models.Tariff) error {
|
||||||
|
if tariff.Name == "" {
|
||||||
|
return errors.New("name is required")
|
||||||
|
}
|
||||||
|
if tariff.Price < 0 {
|
||||||
|
return errors.New("invalid price value")
|
||||||
|
}
|
||||||
|
if len(tariff.Privileges) == 0 {
|
||||||
|
return errors.New("privileges are required")
|
||||||
|
}
|
||||||
|
for _, privilege := range tariff.Privileges {
|
||||||
|
if privilege.PrivilegeID == "" {
|
||||||
|
return errors.New("privilegeID is required in privileges")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
59
internal/utils/authenticator.go
Normal file
59
internal/utils/authenticator.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prefix = "Bearer "
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAuthenticator(jwtUtil *JWT) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
if jwtUtil == nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Invalid arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := authenticate(jwtUtil, c)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticate(jwtUtil *JWT, c *fiber.Ctx) error {
|
||||||
|
jws, err := parseJWSFromRequest(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, validateErr := jwtUtil.Validate(jws)
|
||||||
|
if validateErr != nil {
|
||||||
|
return validateErr
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Locals(models.AuthJWTDecodedUserIDKey, userID)
|
||||||
|
c.Locals(models.AuthJWTDecodedAccessTokenKey, jws)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJWSFromRequest(c *fiber.Ctx) (string, error) {
|
||||||
|
header := c.Get("Authorization")
|
||||||
|
|
||||||
|
if header != "" && strings.HasPrefix(header, prefix) {
|
||||||
|
return strings.TrimPrefix(header, prefix), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token := c.Query("Authorization")
|
||||||
|
if token == "" {
|
||||||
|
return "", errors.New("failed to parse jws from request: no valid token found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
89
internal/utils/jwt.go
Normal file
89
internal/utils/jwt.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"gitea.pena/PenaSide/tariffs/internal/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JWT struct {
|
||||||
|
privateKey []byte
|
||||||
|
publicKey []byte
|
||||||
|
algorithm *jwt.SigningMethodRSA
|
||||||
|
expiresIn time.Duration
|
||||||
|
issuer string
|
||||||
|
audience string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJWT(configuration *models.JWTConfiguration) *JWT {
|
||||||
|
return &JWT{
|
||||||
|
privateKey: []byte(configuration.PrivateKey),
|
||||||
|
publicKey: []byte(configuration.PublicKey),
|
||||||
|
algorithm: jwt.SigningMethodRS256,
|
||||||
|
expiresIn: 15 * time.Minute,
|
||||||
|
issuer: configuration.Issuer,
|
||||||
|
audience: configuration.Audience,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (receiver *JWT) Create(id string) (string, error) {
|
||||||
|
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(receiver.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse private key on <Create> of <JWT>: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"id": id, // Our userID
|
||||||
|
"exp": now.Add(receiver.expiresIn).Unix(), // The expiration time after which the token must be disregarded.
|
||||||
|
"aud": receiver.audience, // Audience
|
||||||
|
"iss": receiver.issuer, // Issuer
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := jwt.NewWithClaims(receiver.algorithm, claims).SignedString(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to sing on <Create> of <JWT>: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (receiver *JWT) Validate(tokenString string) (string, error) {
|
||||||
|
key, err := jwt.ParseRSAPublicKeyFromPEM(receiver.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse rsa public key on <Validate> of <JWT>: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseCallback := func(token *jwt.Token) (any, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %s", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := jwt.Parse(
|
||||||
|
tokenString,
|
||||||
|
parseCallback,
|
||||||
|
jwt.WithAudience(receiver.audience),
|
||||||
|
jwt.WithIssuer(receiver.issuer),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse jwt token on <Validate> of <JWT>: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok || !token.Valid {
|
||||||
|
return "", errors.New("token is invalid on <Validate> of <JWT>")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, ok := claims["id"].(string)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("data is empty or not a string on <Validate> of <JWT>")
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
import type { Config } from "jest";
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
preset: "ts-jest",
|
|
||||||
testEnvironment: "node",
|
|
||||||
moduleDirectories: ["node_modules", "<rootDir>"],
|
|
||||||
moduleNameMapper: {
|
|
||||||
"@/(.*)": "<rootDir>/src/$1",
|
|
||||||
},
|
|
||||||
testTimeout: 15000,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
1
migrations/test/001_tariffs_insert.down.json
Normal file
1
migrations/test/001_tariffs_insert.down.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{ "delete": "tariffs", "deletes": [{ "q": {} }] }]
|
43
migrations/test/001_tariffs_insert.up.json
Normal file
43
migrations/test/001_tariffs_insert.up.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"insert": "tariffs",
|
||||||
|
"ordered": true,
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"_id": {
|
||||||
|
"$oid": "64e6105384368b75221a5c3e"
|
||||||
|
},
|
||||||
|
"name": "100 генераций",
|
||||||
|
"price": 5000,
|
||||||
|
"userId": "64e6106eab16496587435cbf",
|
||||||
|
"isCustom": false,
|
||||||
|
"privileges": [
|
||||||
|
{
|
||||||
|
"name": "100 генераций",
|
||||||
|
"privilegeId": "64e60d28eac51324f2296753",
|
||||||
|
"serviceKey": "templategen-key",
|
||||||
|
"description": "",
|
||||||
|
"amount": 1,
|
||||||
|
"type": "count",
|
||||||
|
"value": "100",
|
||||||
|
"price": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "100 генераций",
|
||||||
|
"privilegeId": "64e60e9094732821e3142eb8",
|
||||||
|
"serviceKey": "shortlink-key",
|
||||||
|
"description": "",
|
||||||
|
"amount": 1,
|
||||||
|
"type": "count",
|
||||||
|
"value": "100",
|
||||||
|
"price": 1000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isDeleted": false,
|
||||||
|
"createdAt": "2023-06-16T08:15:30.336Z",
|
||||||
|
"updatedAt": "2023-06-16T08:15:30.336Z",
|
||||||
|
"deletedAt": "2023-06-16T08:15:30.336Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
1
migrations/test/002_privileges_insert.down.json
Normal file
1
migrations/test/002_privileges_insert.down.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{ "delete": "privileges", "deletes": [{ "q": {} }] }]
|
42
migrations/test/002_privileges_insert.up.json
Normal file
42
migrations/test/002_privileges_insert.up.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"insert": "privileges",
|
||||||
|
"ordered": true,
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"_id": {
|
||||||
|
"$oid": "64e60d28eac51324f2296753"
|
||||||
|
},
|
||||||
|
"name": "100 генераций",
|
||||||
|
"privilegeId": "64e60d28eac51324f2296753",
|
||||||
|
"serviceKey": "templategen-key",
|
||||||
|
"description": "",
|
||||||
|
"amount": 1,
|
||||||
|
"type": "count",
|
||||||
|
"value": "100",
|
||||||
|
"price": 5000,
|
||||||
|
"isDeleted": false,
|
||||||
|
"createdAt": "2023-06-16T08:15:30.336Z",
|
||||||
|
"updatedAt": "2023-06-16T08:15:30.336Z",
|
||||||
|
"deletedAt": "2023-06-16T08:15:30.336Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": {
|
||||||
|
"$oid": "64e60e9094732821e3142eb8"
|
||||||
|
},
|
||||||
|
"name": "100 генераций",
|
||||||
|
"privilegeId": "64e60e9094732821e3142eb8",
|
||||||
|
"serviceKey": "shortlink-key",
|
||||||
|
"description": "",
|
||||||
|
"amount": 1,
|
||||||
|
"type": "count",
|
||||||
|
"value": "100",
|
||||||
|
"price": 1000,
|
||||||
|
"isDeleted": false,
|
||||||
|
"createdAt": "2023-06-16T08:15:30.336Z",
|
||||||
|
"updatedAt": "2023-06-16T08:15:30.336Z",
|
||||||
|
"deletedAt": "2023-06-16T08:15:30.336Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"ignore": ["test", ".git", "node_modules"],
|
|
||||||
"watch": ["src"],
|
|
||||||
"exec": "npm run start",
|
|
||||||
"ext": "ts",
|
|
||||||
"env": {
|
|
||||||
"ENVIRONMENT": "development"
|
|
||||||
}
|
|
||||||
}
|
|
72
package.json
72
package.json
@ -1,72 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "auth-server",
|
|
||||||
"version": "1.1.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"author": "DevMArt",
|
|
||||||
"license": "ISC",
|
|
||||||
"scripts": {
|
|
||||||
"setup": "yarn & husky install",
|
|
||||||
"start": "ts-node ./src/index.ts",
|
|
||||||
"start:prod": "node -r ts-node/register/transpile-only -r tsconfig-paths/register ./src/index.js",
|
|
||||||
"dev": "nodemon",
|
|
||||||
"test": "jest --coverage",
|
|
||||||
"test:watch": "jest --watch",
|
|
||||||
"build": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
|
||||||
"compose:dev:start": "docker-compose -f deployments/dev/docker-compose.yaml up -d",
|
|
||||||
"compose:dev:stop": "docker-compose -f deployments/dev/docker-compose.yaml down --volumes --rmi local",
|
|
||||||
"code:check": "prettier --check \"src/**/*.{ts,tsx,js,css,scss,html}\"",
|
|
||||||
"code:format": "prettier --write \"src/**/*.{ts,tsx,js,css,scss,html}\"",
|
|
||||||
"code:format:specific-file": "prettier --write",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"lint:fix": "eslint --fix ."
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/cookie": "^8.3.0",
|
|
||||||
"@fastify/jwt": "^6.3.3",
|
|
||||||
"@fastify/swagger": "^8.2.1",
|
|
||||||
"@fastify/swagger-ui": "^1.3.0",
|
|
||||||
"axios": "^1.2.1",
|
|
||||||
"bcryptjs": "^2.4.3",
|
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"fastify": "^4.9.2",
|
|
||||||
"fastify-plugin": "^4.3.0",
|
|
||||||
"fastify-print-routes": "^2.0.6",
|
|
||||||
"jsonwebtoken": "^8.5.1",
|
|
||||||
"mongoose": "^6.7.2",
|
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsconfig-paths": "^4.1.0",
|
|
||||||
"typescript-transform-paths": "^3.4.4"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@commitlint/cli": "^17.1.2",
|
|
||||||
"@commitlint/config-conventional": "^17.1.0",
|
|
||||||
"@types/bcryptjs": "^2.4.2",
|
|
||||||
"@types/jest": "^29.2.3",
|
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
|
||||||
"@types/node": "^18.11.9",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
|
||||||
"eslint": "^8.23.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-import-resolver-node": "^0.3.6",
|
|
||||||
"eslint-import-resolver-typescript": "^3.5.0",
|
|
||||||
"eslint-plugin-import": "^2.26.0",
|
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"husky": "^7.0.4",
|
|
||||||
"jest": "^29.3.1",
|
|
||||||
"jest-mock-extended": "^3.0.4",
|
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"ts-jest": "^29.0.3",
|
|
||||||
"tsc-alias": "^1.8.3",
|
|
||||||
"typescript": "^4.9.3"
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"./src/**/*.{ts,js,jsx,tsx}": [
|
|
||||||
"eslint --ignore-path .gitignore --fix",
|
|
||||||
"prettier --ignore-path .gitignore --write"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
37
pkg/closer/closer.go
Normal file
37
pkg/closer/closer.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package closer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Closer interface {
|
||||||
|
Close(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloserFunc func(ctx context.Context) error
|
||||||
|
|
||||||
|
func (cf CloserFunc) Close(ctx context.Context) error {
|
||||||
|
return cf(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloserGroup struct {
|
||||||
|
closers []Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCloserGroup() *CloserGroup {
|
||||||
|
return &CloserGroup{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cg *CloserGroup) Add(c Closer) {
|
||||||
|
cg.closers = append(cg.closers, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cg *CloserGroup) Call(ctx context.Context) error {
|
||||||
|
var closeErr error
|
||||||
|
for i := len(cg.closers) - 1; i >= 0; i-- {
|
||||||
|
if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil {
|
||||||
|
closeErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closeErr
|
||||||
|
}
|
@ -1,16 +0,0 @@
|
|||||||
import { authService } from "./instance";
|
|
||||||
|
|
||||||
import type { User } from "@/types/models/user.type";
|
|
||||||
import type { GetUserRequest } from "./types";
|
|
||||||
|
|
||||||
export const getUser = async (request: GetUserRequest): Promise<User | null> => {
|
|
||||||
try {
|
|
||||||
const { data } = await authService.get<User>(`/user/${request.id}`);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (nativeError) {
|
|
||||||
console.error(nativeError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
@ -1,7 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import { CONFIGURATION } from "@/constants/configuration";
|
|
||||||
|
|
||||||
export const authService = axios.create({
|
|
||||||
baseURL: `${CONFIGURATION.authService.host}:${CONFIGURATION.authService.port}`,
|
|
||||||
});
|
|
@ -1,3 +0,0 @@
|
|||||||
export type GetUserRequest = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
import { Router } from "@/server/router";
|
|
||||||
|
|
||||||
import { setAccountRoutes } from "@/routes/account.routes";
|
|
||||||
import { setPrivilegeRoutes } from "@/routes/privilege.routes";
|
|
||||||
import { setRoleRoutes } from "@/routes/role.routes";
|
|
||||||
import { setTariffRoutes } from "@/routes/tariff.routes";
|
|
||||||
import { setPermissionRoutes } from "@/routes/permission.routes";
|
|
||||||
|
|
||||||
export const combineRoutes = (router: Router): void => {
|
|
||||||
router.group("/role", setRoleRoutes);
|
|
||||||
router.group("/account", setAccountRoutes);
|
|
||||||
router.group("/privilege", setPrivilegeRoutes);
|
|
||||||
router.group("/tariff", setTariffRoutes);
|
|
||||||
router.group("/permission", setPermissionRoutes);
|
|
||||||
};
|
|
@ -1,19 +0,0 @@
|
|||||||
import path from "path";
|
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
|
|
||||||
import { defineEnvironment } from "./define-environment";
|
|
||||||
|
|
||||||
export const configureENV = () => {
|
|
||||||
const environment = defineEnvironment();
|
|
||||||
|
|
||||||
if (environment === "development") {
|
|
||||||
return dotenv.config({
|
|
||||||
debug: true,
|
|
||||||
path: path.resolve(process.env.PWD || "", ".env.example"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return dotenv.config();
|
|
||||||
};
|
|
||||||
|
|
||||||
configureENV();
|
|
@ -1,7 +0,0 @@
|
|||||||
import type { DatabaseOptions } from "@/types/configuration/database-options";
|
|
||||||
|
|
||||||
export const constituteMongoURI = ({ username, password, host, port, database }: DatabaseOptions) => {
|
|
||||||
const dbConnection = `mongodb://${username}:${password}@${host}:${port}`;
|
|
||||||
|
|
||||||
return database ? `${dbConnection}/${database}` : dbConnection;
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
import type { Environment } from "@/types/environment";
|
|
||||||
|
|
||||||
export const defineEnvironment = (): Environment => {
|
|
||||||
const environments: Environment[] = ["development", "staging", "production"];
|
|
||||||
const environment = process.env.ENVIRONMENT as Environment | undefined;
|
|
||||||
|
|
||||||
if (environment && environments.includes(environment)) {
|
|
||||||
return environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "development";
|
|
||||||
};
|
|
@ -1,21 +0,0 @@
|
|||||||
import cookie from "@fastify/cookie";
|
|
||||||
import jwt from "@fastify/jwt";
|
|
||||||
import swagger from "@fastify/swagger";
|
|
||||||
import swaggerUI from "@fastify/swagger-ui";
|
|
||||||
|
|
||||||
import printRoutes from "@/plugins/print-routes";
|
|
||||||
|
|
||||||
import { DEFAULT } from "@/constants/default";
|
|
||||||
|
|
||||||
import type { FastifyInstance } from "fastify";
|
|
||||||
import type { PluginsOptions } from "@/types/configuration/plugins-options";
|
|
||||||
|
|
||||||
export const registerFastifyPlugins = (fastify: FastifyInstance, options: PluginsOptions) => {
|
|
||||||
fastify.register(cookie, options.cookie);
|
|
||||||
fastify.register(jwt, options.jwt);
|
|
||||||
fastify.register(swagger, DEFAULT.swaggerOptions);
|
|
||||||
fastify.register(swaggerUI, DEFAULT.swaggerUIOptions);
|
|
||||||
fastify.register(printRoutes);
|
|
||||||
|
|
||||||
return fastify;
|
|
||||||
};
|
|
@ -1,21 +0,0 @@
|
|||||||
export const CONFIGURATION = {
|
|
||||||
http: {
|
|
||||||
host: process.env.HTTP_HOST || "",
|
|
||||||
port: Number(process.env.HTTP_PORT) || 8080,
|
|
||||||
},
|
|
||||||
db: {
|
|
||||||
host: process.env.DB_HOST || "",
|
|
||||||
port: Number(process.env.DB_PORT) || 3000,
|
|
||||||
username: process.env.DB_USERNAME || "admin",
|
|
||||||
password: process.env.DB_PASSWORD || "admin",
|
|
||||||
database: process.env.DB_NAME || "database",
|
|
||||||
},
|
|
||||||
service: {
|
|
||||||
publicAccessSecretKey: process.env.PUBLIC_ACCESS_SECRET_KEY || "",
|
|
||||||
salt: Number(process.env.SALT) || 10,
|
|
||||||
},
|
|
||||||
authService: {
|
|
||||||
host: process.env.AUTH_SERVICE_HOST || "",
|
|
||||||
port: Number(process.env.AUTH_SERVICE_PORT) || 8081,
|
|
||||||
},
|
|
||||||
} as const;
|
|
@ -1,46 +0,0 @@
|
|||||||
import type { FastifyServerOptions } from "fastify";
|
|
||||||
import type { FastifyDynamicSwaggerOptions } from "@fastify/swagger";
|
|
||||||
import type { FastifySwaggerUiOptions } from "@fastify/swagger-ui";
|
|
||||||
|
|
||||||
type Default = {
|
|
||||||
readonly fastifyOptions: FastifyServerOptions;
|
|
||||||
readonly swaggerOptions: FastifyDynamicSwaggerOptions;
|
|
||||||
readonly swaggerUIOptions: FastifySwaggerUiOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DEFAULT: Default = {
|
|
||||||
fastifyOptions: {
|
|
||||||
logger: true,
|
|
||||||
ajv: {
|
|
||||||
customOptions: { removeAdditional: "failing" },
|
|
||||||
},
|
|
||||||
bodyLimit: 30 * 1024 * 1024,
|
|
||||||
},
|
|
||||||
swaggerOptions: {
|
|
||||||
openapi: {
|
|
||||||
info: {
|
|
||||||
title: "Hub Admin Backend",
|
|
||||||
description: "Тестирование сервиса админ панели хаба",
|
|
||||||
version: "0.1.0",
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
securitySchemes: {
|
|
||||||
bearer: {
|
|
||||||
description: "Authorization header token, sample: Bearer <token>",
|
|
||||||
type: "http",
|
|
||||||
bearerFormat: "JWT",
|
|
||||||
scheme: "bearer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hideUntagged: true,
|
|
||||||
},
|
|
||||||
swaggerUIOptions: {
|
|
||||||
routePrefix: "/swagger",
|
|
||||||
uiConfig: {
|
|
||||||
docExpansion: "full",
|
|
||||||
deepLinking: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
import type { Permission } from "@/types/models/permission.type";
|
|
||||||
|
|
||||||
const DELETE_ACCOUNT_PERMISSION: Permission = {
|
|
||||||
name: "hub-admin-backend-service.deleteAccount",
|
|
||||||
description: "Разрешение на удаление чужого аккаунта пользователем",
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
isDeleted: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PERMISSIONS = {
|
|
||||||
deleteAccount: DELETE_ACCOUNT_PERMISSION.name,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const PERMISSION_LIST: Array<Permission> = [DELETE_ACCOUNT_PERMISSION];
|
|
@ -1,234 +0,0 @@
|
|||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
import { AccountModel } from "@/models/account.model";
|
|
||||||
import { RoleModel } from "@/models/role.model";
|
|
||||||
import { PermissionModule } from "@/services/permission/permission.module";
|
|
||||||
|
|
||||||
import { getUser } from "@/clients/auth";
|
|
||||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
|
||||||
import { determinePaginationParameters } from "@/utils/determine-pagination-parameters";
|
|
||||||
|
|
||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
||||||
import type { Account } from "@/types/models/account.type";
|
|
||||||
import type { GetAccountRequest, SetAccountRoleRequest, GetAccountsRequest, GetAccountsResponse } from "./types";
|
|
||||||
|
|
||||||
export const getAccounts = async (request: GetAccountsRequest): Promise<GetAccountsResponse> => {
|
|
||||||
const { page, limit } = determinePaginationParameters(request?.query ?? {});
|
|
||||||
|
|
||||||
const accountsCount = await AccountModel.countDocuments();
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(accountsCount / limit);
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
const accounts = await AccountModel.find({}).sort({ createdAt: "desc" }).skip(offset).limit(limit).lean();
|
|
||||||
|
|
||||||
return { accounts, totalPages };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createAccount = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
||||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findOne({ userId: request.user.id }).lean();
|
|
||||||
|
|
||||||
if (account) {
|
|
||||||
void reply.status(409);
|
|
||||||
return new Error("account already exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await getUser({ id: request.user.id });
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("user not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdAccount = new AccountModel({
|
|
||||||
userId: user._id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return createdAccount.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccountByID = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
|
||||||
const [getAccountRequestParams, error] = validateEmptyFields(request.params ?? {}, ["userId"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(getAccountRequestParams.userId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findOne({ userId: getAccountRequestParams.userId }).lean();
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccount = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
|
||||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findOne({ userId: request.user.id }).lean();
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setAccountRole = async (request: SetAccountRoleRequest, reply: FastifyReply) => {
|
|
||||||
const [setAccountRoleBody, error] = validateEmptyFields(request.body || {}, ["userId", "role"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await RoleModel.findOne({ name: setAccountRoleBody.role }).lean();
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findOneAndUpdate(
|
|
||||||
{ userId: setAccountRoleBody.userId },
|
|
||||||
{ $set: { role: role.name } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeAccount = async (request: FastifyRequest, reply: FastifyReply): Promise<Account> => {
|
|
||||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findOneAndUpdate(
|
|
||||||
{ userId: request.user.id },
|
|
||||||
{ $set: { isDeleted: true, deletedAt: new Date() } }
|
|
||||||
).lean();
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeAccountById = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
|
||||||
const [{ userId }, error] = validateEmptyFields(request.params ?? {}, ["userId"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(userId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findOne({ userId: request.user.id });
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAvailableToDelete = await PermissionModule.deleteAccount(account);
|
|
||||||
|
|
||||||
if (!isAvailableToDelete) {
|
|
||||||
throw new Error("the user doesn't have the permission to delete account");
|
|
||||||
}
|
|
||||||
|
|
||||||
await account.updateOne({ $set: { isDeleted: true, deletedAt: new Date() } });
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteAccount = async (request: FastifyRequest, reply: FastifyReply): Promise<Account> => {
|
|
||||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findByIdAndDelete({ userId: request.user.id }).lean();
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteAccountById = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
|
||||||
const [{ userId }, error] = validateEmptyFields(request.params ?? {}, ["userId"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(userId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findByIdAndDelete({ userId: request.user.id }).lean();
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAvailableToDelete = await PermissionModule.deleteAccount(account);
|
|
||||||
|
|
||||||
if (!isAvailableToDelete) {
|
|
||||||
throw new Error("the user doesn't have the permission to delete account");
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const restoreAccount = async (request: FastifyRequest, reply: FastifyReply): Promise<Account> => {
|
|
||||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid user id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await AccountModel.findOneAndUpdate(
|
|
||||||
{ userId: request.user.id },
|
|
||||||
{ $set: { isDeleted: false } }
|
|
||||||
).lean();
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("account not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return account;
|
|
||||||
};
|
|
@ -1,25 +0,0 @@
|
|||||||
import type { FastifyRequest } from "fastify";
|
|
||||||
import type { Account } from "@/types/models/account.type";
|
|
||||||
import type { PaginationParams } from "@/types/messages/pagination.type";
|
|
||||||
|
|
||||||
export type GetAccountRequest = FastifyRequest<{
|
|
||||||
Params?: {
|
|
||||||
userId?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type SetAccountRoleRequest = FastifyRequest<{
|
|
||||||
Body: {
|
|
||||||
userId?: string;
|
|
||||||
role?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type GetAccountsRequest = FastifyRequest<{
|
|
||||||
Querystring?: PaginationParams;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type GetAccountsResponse = {
|
|
||||||
accounts: Account[];
|
|
||||||
totalPages: number;
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { FastifyRequest, FastifyReply } from "fastify";
|
|
||||||
|
|
||||||
export const verifyUser = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
||||||
try {
|
|
||||||
const { id } = await request.jwtVerify<{ id?: string }>();
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
void reply.status(401);
|
|
||||||
return reply.send("user id is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
request.user = { id };
|
|
||||||
} catch (nativeError) {
|
|
||||||
void reply.status(401);
|
|
||||||
return reply.send(nativeError);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,166 +0,0 @@
|
|||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
import { PermissionModule } from "@/services/permission/permission.module";
|
|
||||||
import { PermissionModel } from "@/models/permission.model";
|
|
||||||
|
|
||||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
|
||||||
|
|
||||||
import type { FastifyReply } from "fastify";
|
|
||||||
import type { Permission } from "@/types/models/permission.type";
|
|
||||||
import type { CreatePermissionRequest, GetPermissionByIdRequest, UpdatePermissionRequest } from "./types";
|
|
||||||
|
|
||||||
export const getAllPermissions = async (): Promise<Permission[]> => PermissionModule.getAllPermissions();
|
|
||||||
|
|
||||||
export const getPermissionById = async (
|
|
||||||
request: GetPermissionByIdRequest,
|
|
||||||
reply: FastifyReply
|
|
||||||
): Promise<Permission | undefined> => {
|
|
||||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params ?? {}, ["permissionId"]);
|
|
||||||
|
|
||||||
if (validateParamsError) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw validateParamsError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(permissionId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid permission id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const permission = await PermissionModel.findOne({
|
|
||||||
_id: new Types.ObjectId(permissionId),
|
|
||||||
isDeleted: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!permission) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("permission not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return permission;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createPermission = async (request: CreatePermissionRequest, reply: FastifyReply): Promise<Permission> => {
|
|
||||||
const [permission, error] = validateEmptyFields(request.body ?? {}, ["name", "description"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPermission = new PermissionModel(permission);
|
|
||||||
|
|
||||||
return newPermission.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updatePermission = async (
|
|
||||||
request: UpdatePermissionRequest,
|
|
||||||
reply: FastifyReply
|
|
||||||
): Promise<Permission | undefined> => {
|
|
||||||
const [permission, validateBodyError] = validateEmptyFields(request.body ?? {}, ["name", "description"]);
|
|
||||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params ?? {}, ["permissionId"]);
|
|
||||||
|
|
||||||
if (validateBodyError) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw validateBodyError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validateParamsError) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw validateParamsError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(permissionId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid permission id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const findedPermission = await PermissionModel.findByIdAndUpdate(permissionId, {
|
|
||||||
$set: {
|
|
||||||
...permission,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!findedPermission) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("permission not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return permission;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removePermission = async (
|
|
||||||
request: GetPermissionByIdRequest,
|
|
||||||
reply: FastifyReply
|
|
||||||
): Promise<Permission | undefined> => {
|
|
||||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params ?? {}, ["permissionId"]);
|
|
||||||
|
|
||||||
if (validateParamsError) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw validateParamsError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(permissionId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid permission id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const permission = await PermissionModel.findOneAndUpdate(
|
|
||||||
{
|
|
||||||
_id: new Types.ObjectId(permissionId),
|
|
||||||
isDeleted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
deletedAt: new Date(),
|
|
||||||
isDeleted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!permission) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("permission not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return permission;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const restorePermission = async (
|
|
||||||
request: GetPermissionByIdRequest,
|
|
||||||
reply: FastifyReply
|
|
||||||
): Promise<Permission | undefined> => {
|
|
||||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params ?? {}, ["permissionId"]);
|
|
||||||
|
|
||||||
if (validateParamsError) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw validateParamsError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(permissionId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid permission id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const permission = await PermissionModel.findOneAndUpdate(
|
|
||||||
{
|
|
||||||
_id: new Types.ObjectId(permissionId),
|
|
||||||
isDeleted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
deletedAt: undefined,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
isDeleted: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!permission) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("permission not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return permission;
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import type { FastifyRequest } from "fastify";
|
|
||||||
import type { Permission } from "@/types/models/permission.type";
|
|
||||||
import type { ObjectWithPossibleFields } from "@/types/object-with-possible-fields";
|
|
||||||
|
|
||||||
export type CreatePermissionRequest = FastifyRequest<{
|
|
||||||
Body?: ObjectWithPossibleFields<Permission>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type UpdatePermissionRequest = FastifyRequest<{
|
|
||||||
Body?: ObjectWithPossibleFields<Permission>;
|
|
||||||
Params?: {
|
|
||||||
permissionId?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type GetPermissionByIdRequest = FastifyRequest<{
|
|
||||||
Params?: {
|
|
||||||
permissionId?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
@ -1,28 +0,0 @@
|
|||||||
import type { Privilege } from "@/types/models/privilege.type";
|
|
||||||
import type { RawPrivilege } from "./types";
|
|
||||||
|
|
||||||
export const validatePrivilege = (privilege: RawPrivilege): Error | null => {
|
|
||||||
const typeValues: typeof privilege.type[] = ["count", "day", "full"];
|
|
||||||
|
|
||||||
if (!typeValues.includes(privilege.type)) {
|
|
||||||
return new Error("invalid <type> value");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(Number(privilege.price))) {
|
|
||||||
return new Error("price must be a number");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const convertPrivilegiesToMap = (privilegies: Privilege[]) => {
|
|
||||||
return privilegies.reduce<Record<string, Privilege[]>>((accamulator, privilege) => {
|
|
||||||
if (!accamulator[privilege.serviceKey]) {
|
|
||||||
accamulator[privilege.serviceKey] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
accamulator[privilege.serviceKey].push(privilege);
|
|
||||||
|
|
||||||
return accamulator;
|
|
||||||
}, {});
|
|
||||||
};
|
|
@ -1,280 +0,0 @@
|
|||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
import { PrivilegeModel } from "@/models/privilege.model";
|
|
||||||
|
|
||||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
|
||||||
import { convertPrivilegiesToMap, validatePrivilege } from "./helpers";
|
|
||||||
|
|
||||||
import type { FastifyReply } from "fastify";
|
|
||||||
import type {
|
|
||||||
GetServicePrivilegiesRequest,
|
|
||||||
RegisterPrivilegeRequest,
|
|
||||||
GetPrivilegeRequest,
|
|
||||||
RegisterPrivilegiesRequest,
|
|
||||||
RemovePrivilegeRequest,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
export const registerPrivilegies = async (request: RegisterPrivilegiesRequest, reply: FastifyReply) => {
|
|
||||||
const [requestBody, errorEmpty] = validateEmptyFields(request.body ?? {}, ["privilegies"]);
|
|
||||||
|
|
||||||
if (errorEmpty) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!requestBody.privilegies.length) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("empty privilege array");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const rawPrivilege of requestBody.privilegies) {
|
|
||||||
const errorInvalid = validatePrivilege(rawPrivilege);
|
|
||||||
|
|
||||||
if (errorInvalid) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorInvalid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePrivilegeRequests = requestBody.privilegies.map(async (privilege) => {
|
|
||||||
await PrivilegeModel.updateOne(
|
|
||||||
{ privilegeId: privilege.privilegeId },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
name: privilege.name,
|
|
||||||
privilegeId: privilege.privilegeId,
|
|
||||||
serviceKey: privilege.serviceKey,
|
|
||||||
description: privilege.description,
|
|
||||||
type: privilege.type,
|
|
||||||
value: privilege.value,
|
|
||||||
price: privilege.price,
|
|
||||||
isDeleted: false,
|
|
||||||
createdAt: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ upsert: true }
|
|
||||||
).lean();
|
|
||||||
|
|
||||||
return privilege;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(updatePrivilegeRequests);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const registerPrivilege = async (request: RegisterPrivilegeRequest, reply: FastifyReply) => {
|
|
||||||
const [requestBody, errorEmpty] = validateEmptyFields(
|
|
||||||
request.body ?? {},
|
|
||||||
["name", "privilegeId", "serviceKey", "type", "description", "value", "amount"],
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errorEmpty) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorInvalid = validatePrivilege(requestBody);
|
|
||||||
|
|
||||||
if (errorInvalid) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
const privilege = await PrivilegeModel.findOne({ privilegeId: requestBody.privilegeId }).lean();
|
|
||||||
|
|
||||||
if (privilege) {
|
|
||||||
void reply.status(409);
|
|
||||||
return new Error("privilege already exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPrivilege = new PrivilegeModel({
|
|
||||||
name: requestBody.name,
|
|
||||||
privilegeId: requestBody.privilegeId,
|
|
||||||
serviceKey: requestBody.serviceKey,
|
|
||||||
description: requestBody.description,
|
|
||||||
type: requestBody.type,
|
|
||||||
value: requestBody.value,
|
|
||||||
price: requestBody.price,
|
|
||||||
});
|
|
||||||
|
|
||||||
return newPrivilege.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAllPrivilegies = async () => PrivilegeModel.find({ isDeleted: false }).lean();
|
|
||||||
|
|
||||||
export const getAllPrivilegiesMap = async () => {
|
|
||||||
const privilegies = await PrivilegeModel.find({ isDeleted: false }).lean();
|
|
||||||
|
|
||||||
if (!privilegies.length) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertPrivilegiesToMap(privilegies);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getServicePrivilegies = async (request: GetServicePrivilegiesRequest, reply: FastifyReply) => {
|
|
||||||
const [requestParams, error] = validateEmptyFields(request.params ?? {}, ["serviceKey"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PrivilegeModel.find({ serviceKey: requestParams.serviceKey, isDeleted: false }).lean();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => {
|
|
||||||
const [requestParams, error] = validateEmptyFields(request.params ?? {}, ["privilegeId"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(requestParams.privilegeId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const privilege = await PrivilegeModel.findOne({ privilegeId: requestParams.privilegeId, isDeleted: false }).lean();
|
|
||||||
|
|
||||||
if (!privilege) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("privilege not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return privilege;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const replacePrivilege = async (request: RegisterPrivilegeRequest, reply: FastifyReply) => {
|
|
||||||
const [requestBody, errorEmpty] = validateEmptyFields(
|
|
||||||
request.body ?? {},
|
|
||||||
["name", "privilegeId", "serviceKey", "type", "description", "value", "amount"],
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errorEmpty) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorInvalid = validatePrivilege(requestBody);
|
|
||||||
|
|
||||||
if (errorInvalid) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
const privilege = await PrivilegeModel.findOne({ privilegeId: requestBody.privilegeId });
|
|
||||||
|
|
||||||
if (!privilege) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("privilege not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await privilege.replaceOne({
|
|
||||||
name: requestBody.name,
|
|
||||||
privilegeId: requestBody.privilegeId,
|
|
||||||
serviceKey: requestBody.serviceKey,
|
|
||||||
description: requestBody.description,
|
|
||||||
type: requestBody.type,
|
|
||||||
value: requestBody.value,
|
|
||||||
price: requestBody.price,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return privilege;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removePrivilege = async (request: RemovePrivilegeRequest, reply: FastifyReply) => {
|
|
||||||
const [{ privilegeId }, error] = validateEmptyFields(request.body ?? {}, ["privilegeId"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(privilegeId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const privilege = await PrivilegeModel.findOneAndUpdate(
|
|
||||||
{ privilegeId },
|
|
||||||
{ $set: { isDeleted: true, deletedAt: new Date() } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!privilege) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("privilege not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return privilege;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const replacePrivilegies = async (request: RegisterPrivilegiesRequest, reply: FastifyReply) => {
|
|
||||||
const [requestBody, errorEmpty] = validateEmptyFields(request.body ?? {}, ["privilegies"]);
|
|
||||||
|
|
||||||
if (errorEmpty) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!requestBody.privilegies.length) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("empty privilege array");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const rawPrivilege of requestBody.privilegies) {
|
|
||||||
const errorInvalid = validatePrivilege(rawPrivilege);
|
|
||||||
|
|
||||||
if (errorInvalid) {
|
|
||||||
void reply.status(400);
|
|
||||||
return errorInvalid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const replacePrivilegeRequests = requestBody.privilegies.map(async (privilege) => {
|
|
||||||
await PrivilegeModel.replaceOne(
|
|
||||||
{ privilegeId: privilege.privilegeId },
|
|
||||||
{
|
|
||||||
name: privilege.name,
|
|
||||||
privilegeId: privilege.privilegeId,
|
|
||||||
serviceKey: privilege.serviceKey,
|
|
||||||
description: privilege.description,
|
|
||||||
type: privilege.type,
|
|
||||||
value: privilege.value,
|
|
||||||
price: privilege.price,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
isDeleted: false,
|
|
||||||
}
|
|
||||||
).lean();
|
|
||||||
|
|
||||||
return privilege;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(replacePrivilegeRequests);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const restorePrivilege = async (request: RemovePrivilegeRequest, reply: FastifyReply) => {
|
|
||||||
const [{ privilegeId }, error] = validateEmptyFields(request.body ?? {}, ["privilegeId"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(privilegeId)) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const privilege = await PrivilegeModel.findOneAndUpdate({ privilegeId }, { $set: { isDeleted: false } });
|
|
||||||
|
|
||||||
if (!privilege) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("privilege not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return privilege;
|
|
||||||
};
|
|
@ -1,33 +0,0 @@
|
|||||||
import type { FastifyRequest } from "fastify";
|
|
||||||
import type { Privilege } from "@/types/models/privilege.type";
|
|
||||||
import type { Eloquent } from "@/types/models/eloquent.type";
|
|
||||||
|
|
||||||
export type RawPrivilege = Partial<Omit<Privilege, keyof Eloquent>>;
|
|
||||||
|
|
||||||
export type RegisterPrivilegeRequest = FastifyRequest<{
|
|
||||||
Body?: RawPrivilege;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type RegisterPrivilegiesRequest = FastifyRequest<{
|
|
||||||
Body?: {
|
|
||||||
privilegies?: RawPrivilege[];
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type GetServicePrivilegiesRequest = FastifyRequest<{
|
|
||||||
Params?: {
|
|
||||||
serviceKey?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type GetPrivilegeRequest = FastifyRequest<{
|
|
||||||
Params?: {
|
|
||||||
privilegeId?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type RemovePrivilegeRequest = FastifyRequest<{
|
|
||||||
Body?: {
|
|
||||||
privilegeId?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
@ -1,225 +0,0 @@
|
|||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
import { RoleModel } from "@/models/role.model";
|
|
||||||
|
|
||||||
import { convertArrayToRecord } from "@/utils/convert-array-to-record";
|
|
||||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
|
||||||
|
|
||||||
import type { FastifyReply } from "fastify";
|
|
||||||
import type { CreateRoleRequest, GetRoleRequest, RemoveRoleRequest, UpdateRoleRequest } from "./types";
|
|
||||||
|
|
||||||
export const getAllRoles = async () => RoleModel.find({ isDeleted: false }).lean();
|
|
||||||
|
|
||||||
export const createRole = async (request: CreateRoleRequest, reply: FastifyReply) => {
|
|
||||||
const [createRoleRequest, error] = validateEmptyFields(request.body, ["name", "permissions"]);
|
|
||||||
|
|
||||||
if (error || !createRoleRequest) {
|
|
||||||
void reply.status(400);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isRoleExist = await RoleModel.exists({ name: createRoleRequest.name }).lean();
|
|
||||||
|
|
||||||
if (isRoleExist) {
|
|
||||||
void reply.status(409);
|
|
||||||
return new Error("role already exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissionsRecord = convertArrayToRecord(createRoleRequest.permissions, true);
|
|
||||||
|
|
||||||
return RoleModel.create({ name: createRoleRequest.name, permissions: permissionsRecord });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteRole = async (request: RemoveRoleRequest, reply: FastifyReply) => {
|
|
||||||
const { id } = request.body || {};
|
|
||||||
|
|
||||||
if (!id || !Types.ObjectId.isValid(id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("wrong id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedRole = await RoleModel.findByIdAndDelete(id);
|
|
||||||
|
|
||||||
if (!deletedRole) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return deletedRole.toJSON();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeRole = async (request: RemoveRoleRequest, reply: FastifyReply) => {
|
|
||||||
const { id } = request.body || {};
|
|
||||||
|
|
||||||
if (!id || !Types.ObjectId.isValid(id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("wrong id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await RoleModel.findById(id);
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role by id not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role.isDeleted) {
|
|
||||||
void reply.status(409);
|
|
||||||
return new Error("role already deleted");
|
|
||||||
}
|
|
||||||
|
|
||||||
await role.updateOne({ $set: { isDeleted: true, deletedAt: Date.now() } });
|
|
||||||
|
|
||||||
return role;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getRole = async (request: GetRoleRequest, reply: FastifyReply) => {
|
|
||||||
const { query } = request.params || {};
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("query is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Types.ObjectId.isValid(query)) {
|
|
||||||
const role = await RoleModel.findOne({ _id: new Types.ObjectId(query), isDeleted: false }).lean();
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await RoleModel.findOne({ name: query, isDeleted: false }).lean();
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return role;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const replaceRole = async (request: UpdateRoleRequest, reply: FastifyReply) => {
|
|
||||||
const [replaceRoleRequest, error] = validateEmptyFields(request.body ?? {}, ["name", "permissions"]);
|
|
||||||
const { query } = request.params || {};
|
|
||||||
|
|
||||||
if (error || !replaceRoleRequest) {
|
|
||||||
void reply.status(400);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("either name or id must be filled");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Types.ObjectId.isValid(query)) {
|
|
||||||
const role = await RoleModel.findById(query);
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role by id not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await role.replaceOne({
|
|
||||||
name: replaceRoleRequest.name,
|
|
||||||
permissions: convertArrayToRecord(replaceRoleRequest.permissions, true),
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await RoleModel.findOne({ name: query });
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await role.replaceOne({
|
|
||||||
name: replaceRoleRequest.name,
|
|
||||||
permissions: convertArrayToRecord(replaceRoleRequest.permissions, true),
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return role;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateRole = async (request: UpdateRoleRequest, reply: FastifyReply) => {
|
|
||||||
const { query } = request.params;
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("query is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.body?.name && !request.body?.permissions) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("either name or permissions must be filled");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Types.ObjectId.isValid(query)) {
|
|
||||||
const role = await RoleModel.findById(query);
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role by id not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await role.updateOne({
|
|
||||||
$set: {
|
|
||||||
name: request.body?.name,
|
|
||||||
permissions: request.body?.permissions && convertArrayToRecord(request.body.permissions, true),
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await RoleModel.findOne({ name: query });
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await role.updateOne({
|
|
||||||
$set: {
|
|
||||||
name: request.body?.name,
|
|
||||||
permissions: request.body?.permissions && convertArrayToRecord(request.body.permissions, true),
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return role;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const restoreRole = async (request: RemoveRoleRequest, reply: FastifyReply) => {
|
|
||||||
const { id } = request.body || {};
|
|
||||||
|
|
||||||
if (!id || !Types.ObjectId.isValid(id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
return new Error("wrong id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await RoleModel.findById(id);
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
void reply.status(404);
|
|
||||||
return new Error("role by id not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!role.isDeleted) {
|
|
||||||
void reply.status(409);
|
|
||||||
return new Error("role not removed");
|
|
||||||
}
|
|
||||||
|
|
||||||
await role.updateOne({ $set: { isDeleted: false } });
|
|
||||||
|
|
||||||
return role;
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
import type { FastifyRequest } from "fastify";
|
|
||||||
|
|
||||||
type RoleRequest = {
|
|
||||||
name?: string;
|
|
||||||
permissions?: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CreateRoleRequest = FastifyRequest<{ Body: RoleRequest }>;
|
|
||||||
export type RemoveRoleRequest = FastifyRequest<{ Body: { id?: string } }>;
|
|
||||||
|
|
||||||
export type GetRoleRequest = FastifyRequest<{
|
|
||||||
Params: {
|
|
||||||
query?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type UpdateRoleRequest = FastifyRequest<{
|
|
||||||
Body?: RoleRequest;
|
|
||||||
Params: {
|
|
||||||
query?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
@ -1,18 +0,0 @@
|
|||||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
|
||||||
|
|
||||||
import type { TariffMessage } from "@/types/messages/tariff-message.type";
|
|
||||||
import type { ObjectWithRequiredFields } from "@/types/object-with-required-fields";
|
|
||||||
|
|
||||||
export const validateTariff = (tariff?: TariffMessage): [ObjectWithRequiredFields<TariffMessage>, Error | null] => {
|
|
||||||
const [validatedTariff, errorEmpty] = validateEmptyFields(tariff ?? {}, ["isCustom", "name", "privilegies"], false);
|
|
||||||
|
|
||||||
if (errorEmpty) {
|
|
||||||
return [validatedTariff, errorEmpty];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validatedTariff.price && isNaN(Number(validatedTariff.price))) {
|
|
||||||
return [validatedTariff, new Error("invalid 'price' value")];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [validatedTariff, null];
|
|
||||||
};
|
|
@ -1,221 +0,0 @@
|
|||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
import { TariffModel } from "@/models/tariff.model";
|
|
||||||
import { PrivilegeModel } from "@/models/privilege.model";
|
|
||||||
|
|
||||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
|
||||||
import { determinePaginationParameters } from "@/utils/determine-pagination-parameters";
|
|
||||||
import { validateTariff } from "./helpers";
|
|
||||||
|
|
||||||
import type { FastifyReply } from "fastify";
|
|
||||||
import type { Privilege } from "@/types/models/privilege.type";
|
|
||||||
import type { Tariff } from "@/types/models/tariff.type";
|
|
||||||
import type { Eloquent } from "@/types/models/eloquent.type";
|
|
||||||
import type {
|
|
||||||
CreateTariffRequest,
|
|
||||||
GetTariffRequest,
|
|
||||||
ReplaceTariffRequest,
|
|
||||||
RemoveTariffRequest,
|
|
||||||
GetTariffsResponse,
|
|
||||||
GetTariffsRequest,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
export const getTariffs = async (request: GetTariffsRequest): Promise<GetTariffsResponse> => {
|
|
||||||
const { page, limit } = determinePaginationParameters(request?.query ?? {});
|
|
||||||
|
|
||||||
const tariffsCount = await TariffModel.countDocuments({
|
|
||||||
$or: [{ isCustom: false }, { isCustom: true, userId: request.user.id }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(tariffsCount / limit);
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
const tariffs = await TariffModel.find({ $or: [{ isCustom: false }, { isCustom: true, userId: request.user.id }] })
|
|
||||||
.sort({ createdAt: "desc" })
|
|
||||||
.skip(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
return { tariffs, totalPages };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTariff = async (request: GetTariffRequest, reply: FastifyReply): Promise<Tariff> => {
|
|
||||||
const [requestParams, error] = validateEmptyFields(request.params ?? {}, ["id"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(requestParams.id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tariff = await TariffModel.findOne({
|
|
||||||
$or: [
|
|
||||||
{ _id: new Types.ObjectId(requestParams.id), isCustom: false },
|
|
||||||
{ _id: new Types.ObjectId(requestParams.id), isCustom: true, userId: request.user.id },
|
|
||||||
],
|
|
||||||
}).lean();
|
|
||||||
|
|
||||||
if (!tariff) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("tariff not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return tariff;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createTariff = async (request: CreateTariffRequest, reply: FastifyReply): Promise<Tariff> => {
|
|
||||||
const [requestBody, error] = validateTariff(request.body);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const privilege of requestBody.privilegies) {
|
|
||||||
if (!Types.ObjectId.isValid(privilege.privilegeId)) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error(`privilege id <${privilege.privilegeId}> invalid`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newTariff = new TariffModel({
|
|
||||||
name: requestBody.name,
|
|
||||||
price: requestBody.price,
|
|
||||||
userId: request.user.id,
|
|
||||||
isCustom: requestBody.isCustom,
|
|
||||||
privilegies: requestBody.privilegies,
|
|
||||||
});
|
|
||||||
|
|
||||||
return newTariff.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const replaceTariff = async (request: ReplaceTariffRequest, reply: FastifyReply): Promise<Tariff> => {
|
|
||||||
const [requestBody, error] = validateTariff(request.body ?? {});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(request.params?.id || "")) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tariff = await TariffModel.findById(request.params?.id);
|
|
||||||
|
|
||||||
if (!tariff) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("tariff not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const privilege of requestBody.privilegies) {
|
|
||||||
if (!Types.ObjectId.isValid(privilege.privilegeId)) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error(`privilege id <${privilege.privilegeId}> invalid`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const privilegiesMap = new Map(requestBody.privilegies.map((privilege) => [privilege.privilegeId, privilege]));
|
|
||||||
const privilegeIDs = requestBody.privilegies.map(({ privilegeId }) => privilegeId);
|
|
||||||
const privilegies = await PrivilegeModel.find({ _id: privilegeIDs }).lean();
|
|
||||||
|
|
||||||
const cleanPrivilegies = privilegies.map<Omit<Privilege, keyof Eloquent>>((privilege) => {
|
|
||||||
const currentPrivilege = privilegiesMap.get(privilege._id.toString());
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: privilege.name,
|
|
||||||
privilegeId: privilege.privilegeId,
|
|
||||||
serviceKey: privilege.serviceKey,
|
|
||||||
description: privilege.description,
|
|
||||||
amount: currentPrivilege?.amount ?? 0,
|
|
||||||
type: privilege.type,
|
|
||||||
value: privilege.value,
|
|
||||||
price: privilege.price,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await tariff.replaceOne({
|
|
||||||
name: requestBody.name,
|
|
||||||
price: requestBody.price,
|
|
||||||
isCustom: requestBody.isCustom,
|
|
||||||
privilegies: cleanPrivilegies,
|
|
||||||
});
|
|
||||||
|
|
||||||
return tariff;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeTariff = async (request: RemoveTariffRequest, reply: FastifyReply): Promise<Tariff> => {
|
|
||||||
const [{ id }, error] = validateEmptyFields(request.body ?? {}, ["id"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tariff = await TariffModel.findByIdAndUpdate(id, {
|
|
||||||
$set: { isDeleted: true, deletedAt: new Date() },
|
|
||||||
}).lean();
|
|
||||||
|
|
||||||
if (!tariff) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("tariff not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return tariff;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteTariff = async (request: RemoveTariffRequest, reply: FastifyReply): Promise<Tariff> => {
|
|
||||||
const [{ id }, error] = validateEmptyFields(request.body ?? {}, ["id"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tariff = await TariffModel.findByIdAndDelete(id).lean();
|
|
||||||
|
|
||||||
if (!tariff) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("tariff not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return tariff;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const restoreTariff = async (request: RemoveTariffRequest, reply: FastifyReply): Promise<Tariff> => {
|
|
||||||
const [{ id }, error] = validateEmptyFields(request.body ?? {}, ["id"]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Types.ObjectId.isValid(id)) {
|
|
||||||
void reply.status(400);
|
|
||||||
throw new Error("invalid id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tariff = await TariffModel.findByIdAndUpdate(id, { $set: { isDeleted: false } }).lean();
|
|
||||||
|
|
||||||
if (!tariff) {
|
|
||||||
void reply.status(404);
|
|
||||||
throw new Error("tariff not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return tariff;
|
|
||||||
};
|
|
@ -1,34 +0,0 @@
|
|||||||
import type { FastifyRequest } from "fastify";
|
|
||||||
import type { TariffMessage } from "@/types/messages/tariff-message.type";
|
|
||||||
import type { Tariff } from "@/types/models/tariff.type";
|
|
||||||
import type { PaginationParams } from "@/types/messages/pagination.type";
|
|
||||||
|
|
||||||
export type GetTariffRequest = FastifyRequest<{
|
|
||||||
Params?: {
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type RemoveTariffRequest = FastifyRequest<{
|
|
||||||
Body?: {
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type CreateTariffRequest = FastifyRequest<{
|
|
||||||
Body?: TariffMessage;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type ReplaceTariffRequest = FastifyRequest<{
|
|
||||||
Body?: TariffMessage;
|
|
||||||
Params?: { id?: string };
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type GetTariffsRequest = FastifyRequest<{
|
|
||||||
Querystring?: PaginationParams;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type GetTariffsResponse = {
|
|
||||||
tariffs: Tariff[];
|
|
||||||
totalPages: number;
|
|
||||||
};
|
|
27
src/index.ts
27
src/index.ts
@ -1,27 +0,0 @@
|
|||||||
import "./configuration/configure-env";
|
|
||||||
|
|
||||||
import { Server } from "./server";
|
|
||||||
|
|
||||||
import { CONFIGURATION } from "@/constants/configuration";
|
|
||||||
|
|
||||||
console.info("server configuration: \n", JSON.stringify(CONFIGURATION, null, 2));
|
|
||||||
console.info("env: \n", JSON.stringify(process.env, null, 2));
|
|
||||||
|
|
||||||
const server = new Server({
|
|
||||||
serverOptions: CONFIGURATION.http,
|
|
||||||
databaseOptions: CONFIGURATION.db,
|
|
||||||
pluginsOptions: {
|
|
||||||
jwt: {
|
|
||||||
secret: {
|
|
||||||
public: CONFIGURATION.service.publicAccessSecretKey,
|
|
||||||
private: "secret",
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
allowedIss: "pena-auth-service",
|
|
||||||
allowedAud: "pena",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
server.start();
|
|
@ -1,36 +0,0 @@
|
|||||||
import { Schema, model, SchemaDefinition } from "mongoose";
|
|
||||||
|
|
||||||
import { eloquentSchema } from "./eloquent.schema";
|
|
||||||
|
|
||||||
import type { Account } from "@/types/models/account.type";
|
|
||||||
|
|
||||||
const schema: SchemaDefinition<Account> = {
|
|
||||||
userId: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
index: { unique: true },
|
|
||||||
},
|
|
||||||
nickname: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: "nickname",
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
type: String,
|
|
||||||
default: "/media/img/no-avatar.png",
|
|
||||||
},
|
|
||||||
role: {
|
|
||||||
type: String,
|
|
||||||
default: "user",
|
|
||||||
},
|
|
||||||
...eloquentSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaSettings = {
|
|
||||||
versionKey: false,
|
|
||||||
collection: "accounts",
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountSchema = new Schema<Account>(schema, schemaSettings);
|
|
||||||
|
|
||||||
export const AccountModel = model("Account", AccountSchema);
|
|
@ -1,25 +0,0 @@
|
|||||||
import { SchemaDefinition } from "mongoose";
|
|
||||||
|
|
||||||
import type { Eloquent } from "@/types/models/eloquent.type";
|
|
||||||
|
|
||||||
export const eloquentSchema: SchemaDefinition<Eloquent> = {
|
|
||||||
createdAt: {
|
|
||||||
type: Date,
|
|
||||||
required: true,
|
|
||||||
default: Date.now,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: Date,
|
|
||||||
required: true,
|
|
||||||
default: Date.now,
|
|
||||||
},
|
|
||||||
deletedAt: {
|
|
||||||
type: Date,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
isDeleted: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,28 +0,0 @@
|
|||||||
import { Schema, model, SchemaDefinition } from "mongoose";
|
|
||||||
|
|
||||||
import { eloquentSchema } from "./eloquent.schema";
|
|
||||||
|
|
||||||
import type { Permission } from "@/types/models/permission.type";
|
|
||||||
|
|
||||||
const schema: SchemaDefinition<Permission> = {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
index: { unique: true },
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: "Разрешение на создание ролей",
|
|
||||||
},
|
|
||||||
...eloquentSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaSettings = {
|
|
||||||
versionKey: false,
|
|
||||||
collection: "permissions",
|
|
||||||
};
|
|
||||||
|
|
||||||
const PermissionSchema = new Schema<Permission>(schema, schemaSettings);
|
|
||||||
|
|
||||||
export const PermissionModel = model("Permission", PermissionSchema);
|
|
@ -1,55 +0,0 @@
|
|||||||
import { Schema, model, SchemaDefinition } from "mongoose";
|
|
||||||
|
|
||||||
import { eloquentSchema } from "./eloquent.schema";
|
|
||||||
|
|
||||||
import type { Privilege } from "@/types/models/privilege.type";
|
|
||||||
|
|
||||||
const schema: SchemaDefinition<Privilege> = {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
privilegeId: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
index: true,
|
|
||||||
},
|
|
||||||
amount: {
|
|
||||||
type: Number,
|
|
||||||
require: false,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
serviceKey: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
index: true,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: "count",
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: "1",
|
|
||||||
},
|
|
||||||
price: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
...eloquentSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaSettings = {
|
|
||||||
versionKey: false,
|
|
||||||
collection: "privilegies",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PrivilegeSchema = new Schema<Privilege>(schema, schemaSettings);
|
|
||||||
|
|
||||||
export const PrivilegeModel = model("Privilege", PrivilegeSchema);
|
|
@ -1,27 +0,0 @@
|
|||||||
import { Schema, model, SchemaDefinition } from "mongoose";
|
|
||||||
|
|
||||||
import { eloquentSchema } from "./eloquent.schema";
|
|
||||||
|
|
||||||
import type { Role } from "@/types/models/role.type";
|
|
||||||
|
|
||||||
const schema: SchemaDefinition<Role> = {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
type: Map,
|
|
||||||
of: Boolean,
|
|
||||||
default: {},
|
|
||||||
},
|
|
||||||
...eloquentSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaSettings = {
|
|
||||||
versionKey: false,
|
|
||||||
collection: "roles",
|
|
||||||
};
|
|
||||||
|
|
||||||
const RoleSchema = new Schema<Role>(schema, schemaSettings);
|
|
||||||
|
|
||||||
export const RoleModel = model("Role", RoleSchema);
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Schema, model, SchemaDefinition } from "mongoose";
|
|
||||||
|
|
||||||
import { eloquentSchema } from "./eloquent.schema";
|
|
||||||
import { PrivilegeSchema } from "./privilege.model";
|
|
||||||
|
|
||||||
import type { Tariff } from "@/types/models/tariff.type";
|
|
||||||
|
|
||||||
const schema: SchemaDefinition<Tariff> = {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
price: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isCustom: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
privilegies: {
|
|
||||||
type: [PrivilegeSchema],
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
...eloquentSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaSettings = {
|
|
||||||
versionKey: false,
|
|
||||||
collection: "tariffs",
|
|
||||||
};
|
|
||||||
|
|
||||||
const TariffSchema = new Schema<Tariff>(schema, schemaSettings);
|
|
||||||
|
|
||||||
export const TariffModel = model("Tariff", TariffSchema);
|
|
@ -1,44 +0,0 @@
|
|||||||
import fastifyPlugin from "fastify-plugin";
|
|
||||||
|
|
||||||
import type { FastifyError, FastifyInstance, FastifyPluginOptions, RouteOptions } from "fastify";
|
|
||||||
|
|
||||||
type PrintRoutesRouteOptions = {
|
|
||||||
hide?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const METHODS_ORDER = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS"];
|
|
||||||
|
|
||||||
const printRoutes = (routes: Array<RouteOptions & PrintRoutesRouteOptions> = [], isSwagger = false) => {
|
|
||||||
if (routes.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredRoutes = routes.filter((route) => route.hide !== true);
|
|
||||||
const sortedRoutes = [...filteredRoutes].sort((routeA, routeB) => routeA.url.localeCompare(routeB.url));
|
|
||||||
const output = sortedRoutes.reduce<string>((accamulator, route) => {
|
|
||||||
const methods = Array.isArray(route.method) ? route.method : [route.method];
|
|
||||||
const methodsValue = [...methods].sort((a, b) => METHODS_ORDER.indexOf(a) - METHODS_ORDER.indexOf(b)).join(" | ");
|
|
||||||
|
|
||||||
return accamulator + `${methodsValue}\t${route.url}\n`;
|
|
||||||
}, "");
|
|
||||||
const swaggerOutput = isSwagger ? "GET\t/swagger\n" : "";
|
|
||||||
|
|
||||||
console.info(`\n\nAvailable routes:\n${output + swaggerOutput}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default fastifyPlugin(
|
|
||||||
(instance: FastifyInstance, _: FastifyPluginOptions, done: (error?: FastifyError) => void) => {
|
|
||||||
const routes: Array<RouteOptions> = [];
|
|
||||||
|
|
||||||
instance.addHook("onRoute", (route) => {
|
|
||||||
routes.push(route);
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.addHook("onReady", (next) => {
|
|
||||||
printRoutes(routes, !!instance.swagger);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,50 +0,0 @@
|
|||||||
import { Router } from "@/server/router";
|
|
||||||
|
|
||||||
import {
|
|
||||||
createAccount,
|
|
||||||
getAccountByID,
|
|
||||||
getAccount,
|
|
||||||
setAccountRole,
|
|
||||||
getAccounts,
|
|
||||||
deleteAccount,
|
|
||||||
deleteAccountById,
|
|
||||||
removeAccount,
|
|
||||||
removeAccountById,
|
|
||||||
restoreAccount,
|
|
||||||
} from "@/handlers/account";
|
|
||||||
import { verifyUser } from "@/handlers/auth/middleware";
|
|
||||||
|
|
||||||
import {
|
|
||||||
createAccountSchema,
|
|
||||||
getAccountByIdSchema,
|
|
||||||
getAccountSchema,
|
|
||||||
setAccountRoleSchema,
|
|
||||||
getAccountsSchema,
|
|
||||||
removeAccountSchema,
|
|
||||||
removeAccountByIdSchema,
|
|
||||||
restoreAccountSchema,
|
|
||||||
deleteAccountSchema,
|
|
||||||
deleteAccountByIdSchema,
|
|
||||||
} from "@/swagger/account";
|
|
||||||
|
|
||||||
export const setAccountRoutes = (router: Router): void => {
|
|
||||||
router.get("/pagination", getAccounts, { schema: getAccountsSchema });
|
|
||||||
router.get("/:userId", getAccountByID, { schema: getAccountByIdSchema });
|
|
||||||
router.get("/", getAccount, { preHandler: [verifyUser], schema: getAccountSchema });
|
|
||||||
|
|
||||||
router.post("/", createAccount, { preHandler: [verifyUser], schema: createAccountSchema });
|
|
||||||
router.post("/restore", restoreAccount, { preHandler: [verifyUser], schema: restoreAccountSchema });
|
|
||||||
|
|
||||||
router.patch("/role", setAccountRole, { preHandler: [verifyUser], schema: setAccountRoleSchema });
|
|
||||||
|
|
||||||
router.delete("/", removeAccount, { preHandler: [verifyUser], schema: removeAccountSchema });
|
|
||||||
router.delete("/:userId", removeAccountById, {
|
|
||||||
preHandler: [verifyUser],
|
|
||||||
schema: removeAccountByIdSchema,
|
|
||||||
});
|
|
||||||
router.delete("/delete", deleteAccount, { preHandler: [verifyUser], schema: deleteAccountSchema });
|
|
||||||
router.delete("/delete/:userId", deleteAccountById, {
|
|
||||||
preHandler: [verifyUser],
|
|
||||||
schema: deleteAccountByIdSchema,
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,31 +0,0 @@
|
|||||||
import { Router } from "@/server/router";
|
|
||||||
|
|
||||||
import {
|
|
||||||
getAllPermissions,
|
|
||||||
getPermissionById,
|
|
||||||
createPermission,
|
|
||||||
updatePermission,
|
|
||||||
removePermission,
|
|
||||||
restorePermission,
|
|
||||||
} from "@/handlers/permission";
|
|
||||||
|
|
||||||
import {
|
|
||||||
getAllPermissionsSchema,
|
|
||||||
getPermissionByIdSchema,
|
|
||||||
createPermissionSchema,
|
|
||||||
removePermissionSchema,
|
|
||||||
restorePermissionSchema,
|
|
||||||
updatePermissionSchema,
|
|
||||||
} from "@/swagger/permission";
|
|
||||||
|
|
||||||
export const setPermissionRoutes = (router: Router): void => {
|
|
||||||
router.get("/all", getAllPermissions, { schema: getAllPermissionsSchema });
|
|
||||||
router.get("/:permissionId", getPermissionById, { schema: getPermissionByIdSchema });
|
|
||||||
|
|
||||||
router.post("/", createPermission, { schema: createPermissionSchema });
|
|
||||||
router.post("/restore/:permissionId", restorePermission, { schema: restorePermissionSchema });
|
|
||||||
|
|
||||||
router.patch("/:permissionId", updatePermission, { schema: updatePermissionSchema });
|
|
||||||
|
|
||||||
router.delete("/:permissionId", removePermission, { schema: removePermissionSchema });
|
|
||||||
};
|
|
@ -1,40 +0,0 @@
|
|||||||
import { Router } from "@/server/router";
|
|
||||||
|
|
||||||
import {
|
|
||||||
registerPrivilege,
|
|
||||||
getAllPrivilegies,
|
|
||||||
getAllPrivilegiesMap,
|
|
||||||
getPrivilege,
|
|
||||||
getServicePrivilegies,
|
|
||||||
replacePrivilege,
|
|
||||||
registerPrivilegies,
|
|
||||||
removePrivilege,
|
|
||||||
replacePrivilegies,
|
|
||||||
restorePrivilege,
|
|
||||||
} from "@/handlers/privilege";
|
|
||||||
|
|
||||||
import {
|
|
||||||
getPrivilegiesSchema,
|
|
||||||
getPrivilegiesMapSchema,
|
|
||||||
getPrivilegeSchema,
|
|
||||||
getServicePrivilegiesSchema,
|
|
||||||
registerPrivilegeSchema,
|
|
||||||
registerPrivilegiesSchema,
|
|
||||||
replacePrivilegeSchema,
|
|
||||||
replacePrivilegiesSchema,
|
|
||||||
removePrivilegeSchema,
|
|
||||||
restorePrivilegeSchema,
|
|
||||||
} from "@/swagger/privilege";
|
|
||||||
|
|
||||||
export const setPrivilegeRoutes = (router: Router): void => {
|
|
||||||
router.get("/", getAllPrivilegies, { schema: getPrivilegiesSchema });
|
|
||||||
router.get("/service", getAllPrivilegiesMap, { schema: getPrivilegiesMapSchema });
|
|
||||||
router.get("/:privilegeId", getPrivilege, { schema: getPrivilegeSchema });
|
|
||||||
router.get("/service/:serviceKey", getServicePrivilegies, { schema: getServicePrivilegiesSchema });
|
|
||||||
router.post("/", registerPrivilege, { schema: registerPrivilegeSchema });
|
|
||||||
router.post("/many", registerPrivilegies, { schema: registerPrivilegiesSchema });
|
|
||||||
router.post("/restore/", restorePrivilege, { schema: restorePrivilegeSchema });
|
|
||||||
router.put("/", replacePrivilege, { schema: replacePrivilegeSchema });
|
|
||||||
router.put("/many", replacePrivilegies, { schema: replacePrivilegiesSchema });
|
|
||||||
router.delete("/", removePrivilege, { schema: removePrivilegeSchema });
|
|
||||||
};
|
|
@ -1,34 +0,0 @@
|
|||||||
import { Router } from "@/server/router";
|
|
||||||
|
|
||||||
import {
|
|
||||||
getRole,
|
|
||||||
getAllRoles,
|
|
||||||
createRole,
|
|
||||||
deleteRole,
|
|
||||||
updateRole,
|
|
||||||
replaceRole,
|
|
||||||
removeRole,
|
|
||||||
restoreRole,
|
|
||||||
} from "@/handlers/roles";
|
|
||||||
|
|
||||||
import {
|
|
||||||
getRolesSchema,
|
|
||||||
getRoleSchema,
|
|
||||||
createRoleSchema,
|
|
||||||
restoreRoleSchema,
|
|
||||||
updateRoleSchema,
|
|
||||||
replaceRoleSchema,
|
|
||||||
removeRoleSchema,
|
|
||||||
deleteRoleSchema,
|
|
||||||
} from "@/swagger/role";
|
|
||||||
|
|
||||||
export const setRoleRoutes = (router: Router): void => {
|
|
||||||
router.get("/", getAllRoles, { schema: getRolesSchema });
|
|
||||||
router.get("/:query", getRole, { schema: getRoleSchema });
|
|
||||||
router.post("/", createRole, { schema: createRoleSchema });
|
|
||||||
router.post("/restore", restoreRole, { schema: restoreRoleSchema });
|
|
||||||
router.patch("/:query", updateRole, { schema: updateRoleSchema });
|
|
||||||
router.put("/:query", replaceRole, { schema: replaceRoleSchema });
|
|
||||||
router.delete("/", removeRole, { schema: removeRoleSchema });
|
|
||||||
router.delete("/delete", deleteRole, { schema: deleteRoleSchema });
|
|
||||||
};
|
|
@ -1,32 +0,0 @@
|
|||||||
import { Router } from "@/server/router";
|
|
||||||
|
|
||||||
import {
|
|
||||||
createTariff,
|
|
||||||
replaceTariff,
|
|
||||||
getTariff,
|
|
||||||
getTariffs,
|
|
||||||
removeTariff,
|
|
||||||
restoreTariff,
|
|
||||||
deleteTariff,
|
|
||||||
} from "@/handlers/tariff";
|
|
||||||
import { verifyUser } from "@/handlers/auth/middleware";
|
|
||||||
|
|
||||||
import {
|
|
||||||
getTariffSchema,
|
|
||||||
getTariffsSchema,
|
|
||||||
createTariffsSchema,
|
|
||||||
replaceTariffsSchema,
|
|
||||||
removeTariffsSchema,
|
|
||||||
restoreTariffsSchema,
|
|
||||||
deleteTariffsSchema,
|
|
||||||
} from "@/swagger/tariff";
|
|
||||||
|
|
||||||
export const setTariffRoutes = (router: Router): void => {
|
|
||||||
router.get("/", getTariffs, { preHandler: [verifyUser], schema: getTariffsSchema });
|
|
||||||
router.get("/:id", getTariff, { preHandler: [verifyUser], schema: getTariffSchema });
|
|
||||||
router.post("/", createTariff, { preHandler: [verifyUser], schema: createTariffsSchema });
|
|
||||||
router.post("/restore", restoreTariff, { preHandler: [verifyUser], schema: restoreTariffsSchema });
|
|
||||||
router.put("/:id", replaceTariff, { preHandler: [verifyUser], schema: replaceTariffsSchema });
|
|
||||||
router.delete("/", removeTariff, { preHandler: [verifyUser], schema: removeTariffsSchema });
|
|
||||||
router.delete("/delete", deleteTariff, { preHandler: [verifyUser], schema: deleteTariffsSchema });
|
|
||||||
};
|
|
@ -1,61 +0,0 @@
|
|||||||
import fastify, { FastifyInstance } from "fastify";
|
|
||||||
import { connect as mongoConnect } from "mongoose";
|
|
||||||
|
|
||||||
import { Router } from "./router";
|
|
||||||
|
|
||||||
import { registerFastifyPlugins } from "@/configuration/register-fastify-plugins";
|
|
||||||
import { combineRoutes } from "@/configuration/combine-routes";
|
|
||||||
import { constituteMongoURI } from "@/configuration/constitute-mongo-uri";
|
|
||||||
|
|
||||||
import { DEFAULT } from "@/constants/default";
|
|
||||||
|
|
||||||
import type { PluginsOptions } from "@/types/configuration/plugins-options";
|
|
||||||
import type { DatabaseOptions } from "@/types/configuration/database-options";
|
|
||||||
|
|
||||||
type ServerOptions = {
|
|
||||||
port?: number;
|
|
||||||
host?: string;
|
|
||||||
backlog?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ServerArgs = {
|
|
||||||
serverOptions?: ServerOptions;
|
|
||||||
pluginsOptions?: PluginsOptions;
|
|
||||||
databaseOptions?: DatabaseOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Server {
|
|
||||||
private fastify: FastifyInstance;
|
|
||||||
private serverOptions?: ServerOptions;
|
|
||||||
private databaseOptions?: DatabaseOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Настройка сервера
|
|
||||||
* @param {ServerOptions} serverOptions объект настроек сервера.
|
|
||||||
* @param {DatabaseOptions} databaseOptions объект настроек подключения к базе данных.
|
|
||||||
* @param {PluginsOptions} pluginsOptions объект настроек плагинов fastify.
|
|
||||||
*/
|
|
||||||
constructor({ serverOptions, databaseOptions, pluginsOptions }: ServerArgs) {
|
|
||||||
this.serverOptions = serverOptions;
|
|
||||||
this.databaseOptions = databaseOptions;
|
|
||||||
this.fastify = fastify(DEFAULT.fastifyOptions);
|
|
||||||
|
|
||||||
if (pluginsOptions) {
|
|
||||||
registerFastifyPlugins(this.fastify, pluginsOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
combineRoutes(new Router(this.fastify));
|
|
||||||
}
|
|
||||||
|
|
||||||
public start = async () => {
|
|
||||||
const fasticyConnection = this.fastify.listen(this.serverOptions);
|
|
||||||
const databaseConnection = this.databaseOptions ? mongoConnect(constituteMongoURI(this.databaseOptions)) : null;
|
|
||||||
|
|
||||||
await Promise.all([databaseConnection, fasticyConnection])
|
|
||||||
.then(() => {
|
|
||||||
this.fastify.swagger();
|
|
||||||
console.info(`server started on ${this.serverOptions?.host}:${this.serverOptions?.port}`);
|
|
||||||
})
|
|
||||||
.catch((reason) => console.error(reason));
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
import type {
|
|
||||||
FastifyInstance,
|
|
||||||
RouteHandlerMethod,
|
|
||||||
RawRequestDefaultExpression,
|
|
||||||
RawReplyDefaultExpression,
|
|
||||||
RouteShorthandOptions,
|
|
||||||
FastifyPluginOptions,
|
|
||||||
RawServerDefault,
|
|
||||||
RouteGenericInterface,
|
|
||||||
} from "fastify";
|
|
||||||
|
|
||||||
type HandlerMethod<T extends RouteGenericInterface> = RouteHandlerMethod<
|
|
||||||
RawServerDefault,
|
|
||||||
RawRequestDefaultExpression<RawServerDefault>,
|
|
||||||
RawReplyDefaultExpression<RawServerDefault>,
|
|
||||||
T
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class Router {
|
|
||||||
private fastifyInstance: FastifyInstance;
|
|
||||||
|
|
||||||
constructor(fastifyInstance: FastifyInstance) {
|
|
||||||
this.fastifyInstance = fastifyInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public group = (path: string, combineRoutes: (router: Router) => void) => {
|
|
||||||
this.fastifyInstance.register(
|
|
||||||
(server: FastifyInstance, opts: FastifyPluginOptions, done: () => void) => {
|
|
||||||
const router = new Router(server);
|
|
||||||
|
|
||||||
combineRoutes(router);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
{ prefix: path }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
public get = <T extends RouteGenericInterface>(
|
|
||||||
path: string,
|
|
||||||
handler: HandlerMethod<T>,
|
|
||||||
options: RouteShorthandOptions = {}
|
|
||||||
) => {
|
|
||||||
this.fastifyInstance.get(path, options, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
public post = <T extends RouteGenericInterface>(
|
|
||||||
path: string,
|
|
||||||
handler: HandlerMethod<T>,
|
|
||||||
options: RouteShorthandOptions = {}
|
|
||||||
) => {
|
|
||||||
this.fastifyInstance.post(path, options, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
public put = <T extends RouteGenericInterface>(
|
|
||||||
path: string,
|
|
||||||
handler: HandlerMethod<T>,
|
|
||||||
options: RouteShorthandOptions = {}
|
|
||||||
) => {
|
|
||||||
this.fastifyInstance.put(path, options, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
public patch = <T extends RouteGenericInterface>(
|
|
||||||
path: string,
|
|
||||||
handler: HandlerMethod<T>,
|
|
||||||
options: RouteShorthandOptions = {}
|
|
||||||
) => {
|
|
||||||
this.fastifyInstance.patch(path, options, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
public delete = <T extends RouteGenericInterface>(
|
|
||||||
path: string,
|
|
||||||
handler: HandlerMethod<T>,
|
|
||||||
options: RouteShorthandOptions = {}
|
|
||||||
) => {
|
|
||||||
this.fastifyInstance.delete(path, options, handler);
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { RoleModel } from "@/models/role.model";
|
|
||||||
|
|
||||||
import type { Role } from "@/types/models/role.type";
|
|
||||||
import type { Account } from "@/types/models/account.type";
|
|
||||||
|
|
||||||
export class AccountService {
|
|
||||||
public static async determineAccountRole(account: Account): Promise<Role> {
|
|
||||||
const role = await RoleModel.findOne({ name: account.role }).lean();
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
throw new Error("role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
import type { Account } from "@/types/models/account.type";
|
|
||||||
import type { Role } from "@/types/models/role.type";
|
|
||||||
|
|
||||||
export interface AccountService {
|
|
||||||
determineAccountRole(account: Account): Promise<Role>;
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
import { AccountService } from "@/services/account/account.service";
|
|
||||||
import { PermissionService } from "./permission.service";
|
|
||||||
|
|
||||||
export const PermissionModule = new PermissionService({
|
|
||||||
accountService: AccountService,
|
|
||||||
});
|
|
@ -1,78 +0,0 @@
|
|||||||
import { mock, mockReset } from "jest-mock-extended";
|
|
||||||
|
|
||||||
import { PermissionService } from "./permission.service";
|
|
||||||
|
|
||||||
import { PERMISSIONS } from "@/constants/permissions";
|
|
||||||
|
|
||||||
import type { AccountService } from "./permission.interface";
|
|
||||||
import type { Account } from "@/types/models/account.type";
|
|
||||||
|
|
||||||
const DEFAULT_ACCOUNT: Account = {
|
|
||||||
userId: "userId",
|
|
||||||
nickname: "nickname",
|
|
||||||
avatar: "/img/avatar.png",
|
|
||||||
role: "admin",
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
isDeleted: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("PermissionService", () => {
|
|
||||||
const accountService = mock<AccountService>();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockReset(accountService);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("deleteAccount", () => {
|
|
||||||
test("Успешное определение доступа на удаление аккаунта", async () => {
|
|
||||||
const permissionService = new PermissionService({ accountService });
|
|
||||||
|
|
||||||
accountService.determineAccountRole.mockResolvedValueOnce({
|
|
||||||
name: "admin",
|
|
||||||
permissions: {
|
|
||||||
[PERMISSIONS.deleteAccount]: true,
|
|
||||||
},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
isDeleted: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isAvailableToDeleteAccount = await permissionService.deleteAccount(DEFAULT_ACCOUNT);
|
|
||||||
|
|
||||||
expect(accountService.determineAccountRole).toHaveBeenCalledWith(DEFAULT_ACCOUNT);
|
|
||||||
expect(accountService.determineAccountRole).toHaveBeenCalledTimes(1);
|
|
||||||
expect(isAvailableToDeleteAccount).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("У пользователя нет прав на удаление", async () => {
|
|
||||||
const permissionService = new PermissionService({ accountService });
|
|
||||||
|
|
||||||
accountService.determineAccountRole.mockResolvedValueOnce({
|
|
||||||
name: "admin",
|
|
||||||
permissions: {
|
|
||||||
read: true,
|
|
||||||
},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
isDeleted: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isAvailableToDeleteAccount = await permissionService.deleteAccount(DEFAULT_ACCOUNT);
|
|
||||||
|
|
||||||
expect(accountService.determineAccountRole).toHaveBeenCalledWith(DEFAULT_ACCOUNT);
|
|
||||||
expect(accountService.determineAccountRole).toHaveBeenCalledTimes(1);
|
|
||||||
expect(isAvailableToDeleteAccount).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Ошибка должна успешно пробрасываться", async () => {
|
|
||||||
const permissionService = new PermissionService({ accountService });
|
|
||||||
|
|
||||||
accountService.determineAccountRole.mockRejectedValueOnce(new Error("role not found"));
|
|
||||||
|
|
||||||
expect(permissionService.deleteAccount(DEFAULT_ACCOUNT)).resolves.toBe(false);
|
|
||||||
expect(accountService.determineAccountRole).toHaveBeenCalledWith(DEFAULT_ACCOUNT);
|
|
||||||
expect(accountService.determineAccountRole).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,39 +0,0 @@
|
|||||||
import { PermissionModel } from "@/models/permission.model";
|
|
||||||
|
|
||||||
import { PERMISSIONS, PERMISSION_LIST } from "@/constants/permissions";
|
|
||||||
|
|
||||||
import type { Account } from "@/types/models/account.type";
|
|
||||||
import type { AccountService } from "./permission.interface";
|
|
||||||
import type { Permission } from "@/types/models/permission.type";
|
|
||||||
|
|
||||||
type PermissionServiceDeps = {
|
|
||||||
readonly accountService: AccountService;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PermissionService {
|
|
||||||
private accountService: AccountService;
|
|
||||||
|
|
||||||
constructor(deps: PermissionServiceDeps) {
|
|
||||||
this.accountService = deps.accountService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteAccount(account: Account): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const role = await this.accountService.determineAccountRole(account);
|
|
||||||
|
|
||||||
if (!role.permissions[PERMISSIONS.deleteAccount]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllPermissions(): Promise<Permission[]> {
|
|
||||||
const permissions = await PermissionModel.find({}).lean();
|
|
||||||
|
|
||||||
return [...permissions, ...PERMISSION_LIST];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
import { getAccountParams, setAccountRoleBody, getAccountsQuerystring } from "./inputs";
|
|
||||||
import {
|
|
||||||
getAccountResponse,
|
|
||||||
createAccountResponse,
|
|
||||||
setAccountRoleResponse,
|
|
||||||
getAccountsResponse,
|
|
||||||
removeAccountResponse,
|
|
||||||
} from "./responses";
|
|
||||||
|
|
||||||
import type { SwaggerSchema } from "@/types/swagger.type";
|
|
||||||
|
|
||||||
export const getAccountsSchema: SwaggerSchema = {
|
|
||||||
summary: "Получение информации об аккаунтах",
|
|
||||||
description: "Получение список аккаунтов с пагинацией из БД",
|
|
||||||
tags: ["account"],
|
|
||||||
querystring: getAccountsQuerystring,
|
|
||||||
response: getAccountsResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccountByIdSchema: SwaggerSchema = {
|
|
||||||
summary: "Получение информации об аккаунте",
|
|
||||||
description: "Получение аккаунта по ID",
|
|
||||||
tags: ["account"],
|
|
||||||
params: getAccountParams,
|
|
||||||
response: getAccountResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccountSchema: SwaggerSchema = {
|
|
||||||
summary: "Получение информации об аккаунте",
|
|
||||||
description: "Получение информации об аккаунте через токен доступа",
|
|
||||||
tags: ["account"],
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
response: getAccountResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createAccountSchema: SwaggerSchema = {
|
|
||||||
summary: "Создание аккаунта",
|
|
||||||
tags: ["account"],
|
|
||||||
response: createAccountResponse,
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setAccountRoleSchema: SwaggerSchema = {
|
|
||||||
summary: "Присвоение роли пользователя",
|
|
||||||
tags: ["account"],
|
|
||||||
body: setAccountRoleBody,
|
|
||||||
response: setAccountRoleResponse,
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeAccountSchema: SwaggerSchema = {
|
|
||||||
summary: "Удаление аккаунта",
|
|
||||||
description: "Помечает аккаунт удалённым, но не удаляет его из БД",
|
|
||||||
tags: ["account"],
|
|
||||||
response: removeAccountResponse,
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeAccountByIdSchema: SwaggerSchema = {
|
|
||||||
summary: "Удаление аккаунта по ID",
|
|
||||||
description: "Помечает аккаунт удалённым, но не удаляет его из БД",
|
|
||||||
tags: ["account"],
|
|
||||||
params: getAccountParams,
|
|
||||||
response: removeAccountResponse,
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteAccountSchema: SwaggerSchema = {
|
|
||||||
summary: "Удаление аккаунта",
|
|
||||||
description: "Удаляет аккаунт из БД окончательно",
|
|
||||||
tags: ["account"],
|
|
||||||
response: removeAccountResponse,
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteAccountByIdSchema: SwaggerSchema = {
|
|
||||||
summary: "Удаление аккаунта по ID",
|
|
||||||
description: "Удаляет аккаунт из БД окончательно",
|
|
||||||
tags: ["account"],
|
|
||||||
params: getAccountParams,
|
|
||||||
response: removeAccountResponse,
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const restoreAccountSchema: SwaggerSchema = {
|
|
||||||
summary: "Восстановление аккаунта",
|
|
||||||
description: "Восстанавливает аккаунт, который не был удалён окончательно",
|
|
||||||
tags: ["account"],
|
|
||||||
response: removeAccountResponse,
|
|
||||||
security: [{ bearer: [] }],
|
|
||||||
};
|
|
@ -1,41 +0,0 @@
|
|||||||
import type { SwaggerMessage } from "@/types/swagger.type";
|
|
||||||
|
|
||||||
export const getAccountParams: SwaggerMessage = {
|
|
||||||
type: "object",
|
|
||||||
required: ["userId"],
|
|
||||||
properties: {
|
|
||||||
userId: {
|
|
||||||
type: "string",
|
|
||||||
description: "ID пользователя",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccountsQuerystring: SwaggerMessage = {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
page: {
|
|
||||||
type: "number",
|
|
||||||
description: "номер страницы",
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: "number",
|
|
||||||
description: "Лимит количества аккаунтов (больше 100 не обрабатывается)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setAccountRoleBody: SwaggerMessage = {
|
|
||||||
type: "object",
|
|
||||||
required: ["userId", "role"],
|
|
||||||
properties: {
|
|
||||||
userId: {
|
|
||||||
type: "string",
|
|
||||||
description: "ID пользователя",
|
|
||||||
},
|
|
||||||
role: {
|
|
||||||
type: "string",
|
|
||||||
description: "название роли",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user