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
|
||||
nohup.out
|
||||
.env
|
||||
main
|
||||
/.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
|
||||
|
||||
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/*
|
||||
|
||||
FROM gitea.pena/penadevops/container-images/golang:main as build
|
||||
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
|
||||
COPY --from=dev /usr/app/package.json /app/
|
||||
COPY --from=dev /usr/app/yarn.lock /app/
|
||||
COPY --from=dev /usr/app/tsconfig.json .
|
||||
FROM gitea.pena/penadevops/container-images/alpine:main
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=build app/app .
|
||||
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"
|
||||
|
||||
services:
|
||||
admin:
|
||||
container_name: hub-admin-backend-service
|
||||
tariffs:
|
||||
restart: unless-stopped
|
||||
tty: true
|
||||
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
expose:
|
||||
- 8005
|
||||
networks:
|
||||
- marketplace_penahub_frontend
|
||||
- default
|
||||
image: gitea.pena:3000/penaside/tariffs/staging:$GITHUB_RUN_NUMBER
|
||||
ports:
|
||||
- 10.7.0.6:59303:8005
|
||||
- 10.7.0.6:59304:8006
|
||||
environment:
|
||||
- DB_HOST=10.6.0.11
|
||||
- DB_PORT=27017
|
||||
- ENVIRONMENT=staging
|
||||
- HTTP_HOST=0.0.0.0
|
||||
- HTTP_PORT=8005
|
||||
- AUTH_SERVICE_HOST=http://pena-auth-service
|
||||
- AUTH_SERVICE_PORT=8080
|
||||
- DB_USERNAME=$DB_USERNAME
|
||||
- DB_PASSWORD=$DB_PASSWORD
|
||||
- DB_NAME=administrator
|
||||
- PUBLIC_ACCESS_SECRET_KEY=$PUBLIC_ACCESS_SECRET_KEY
|
||||
networks:
|
||||
marketplace_penahub_frontend:
|
||||
external: true
|
||||
MONGO_URL: mongodb://administrator:64143ffdd9304865586e5cf1@mongodb.pena:27017/administrator?authSource=administrator
|
||||
MONGO_DB_NAME: administrator
|
||||
DB_HOST: 10.7.0.6
|
||||
DB_PORT: 27017
|
||||
ENVIRONMENT: staging
|
||||
INTERNAL_HTTP_ADDRESS: 0.0.0.0:8006
|
||||
EXTERNAL_HTTP_ADDRESS: 0.0.0.0:8005
|
||||
AUTH_SERVICE_HOST: http://10.7.0.4
|
||||
AUTH_SERVICE_PORT: 59300
|
||||
JWT_PUBLIC_KEY: "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLW1tlHyKC9AG0hGpmkksET2DE\nr7ojSPemxFWAgFgcPJWQ7x3uNbsdJ3bIZFoA/FClaWKMCZmjnH9tv0bKZtY/CDhM\nZEyHpMruRSn6IKrxjtQZWy4uv/w6MzUeyBYG0OvNCiYpdvz5SkAGAUHD5ZNFqn2w\nKKFD0I2Dr59BFVSGJwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
PUBLIC_ACCESS_SECRET_KEY: "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLW1tlHyKC9AG0hGpmkksET2DE\nr7ojSPemxFWAgFgcPJWQ7x3uNbsdJ3bIZFoA/FClaWKMCZmjnH9tv0bKZtY/CDhM\nZEyHpMruRSn6IKrxjtQZWy4uv/w6MzUeyBYG0OvNCiYpdvz5SkAGAUHD5ZNFqn2w\nKKFD0I2Dr59BFVSGJwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
|
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