From 0e4a34bb6d0905654dc09126339a4c724f3dba2f Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 19 Dec 2022 17:07:16 +0300 Subject: [PATCH 01/10] docs: readme --- README.md | 28 ++++++++++++++-------------- src/configuration/combine-routes.ts | 1 - 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7d351a2..1a23db1 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ ## Настройка и запуск ``` -1) yarn setup - загрузка всех зависимостей и инициализация husky -2) yarn dev - запуск проекта в режиме разработки +1) yarn setup - загрузка всех зависимостей и инициализация husky +2) yarn dev - запуск проекта в режиме разработки ``` ## Переменные окружения сервиса: @@ -12,26 +12,26 @@ **Для конфигурации сервера**: ``` -ENVIRONMENT - application environment -HTTP_HOST - service host -HTTP_PORT - service port -PUBLIC_ACCESS_SECRET_KEY - secret to verify private access secret key +ENVIRONMENT - application environment +HTTP_HOST - service host +HTTP_PORT - service port +PUBLIC_ACCESS_SECRET_KEY - secret to verify private access secret key ``` **Для конфигурации базы данных**: ``` -DB_HOST - mongo host -DB_PORT - mongo port -DB_USERNAME - mongo username -DB_PASSWORD - mongo password -DB_NAME - database name +DB_HOST - mongo host +DB_PORT - mongo port +DB_USERNAME - mongo username +DB_PASSWORD - mongo password +DB_NAME - database name ``` ## Среды окружения ``` -development - среда для разработки -staging - среда для тестирования продукта -production - среда продакшена +development - среда для разработки +staging - среда для тестирования продукта +production - среда продакшена ``` diff --git a/src/configuration/combine-routes.ts b/src/configuration/combine-routes.ts index ff8ede1..991c215 100644 --- a/src/configuration/combine-routes.ts +++ b/src/configuration/combine-routes.ts @@ -1,4 +1,3 @@ - import { setRoleRoutes } from "@/routes/role.routes"; import type { FastifyInstance } from "fastify"; From ded850436c65310e2639c5986c2b1df90b4c659f Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 20 Dec 2022 15:07:06 +0000 Subject: [PATCH 02/10] feat: account --- .env.example | 4 ++ .eslintrc | 2 +- README.md | 7 +++ package.json | 1 + src/clients/auth/index.ts | 16 ++++++ src/clients/auth/instance.ts | 7 +++ src/clients/auth/types.ts | 3 + src/constants/configuration.ts | 7 ++- src/handlers/account/index.ts | 52 +++++++++++++++++ src/handlers/account/middleware.ts | 19 +++++++ src/handlers/account/types.ts | 6 ++ src/index.ts | 6 +- .../{user.model.ts => account.model.ts} | 23 +++----- src/models/eloquent-model.schema.ts | 2 +- src/models/token.model.ts | 23 -------- src/routes/account.routes.ts | 12 ++++ src/types/fastify-jwt.d.ts | 8 +++ src/types/models/account.type.ts | 7 +++ ...oquent-model.ts => eloquent-model.type.ts} | 0 src/types/models/role.type.ts | 2 +- src/types/models/user.type.ts | 8 +++ src/types/routes/account-routes.type.ts | 13 +++++ yarn.lock | 57 +++++++++++++++++++ 23 files changed, 240 insertions(+), 45 deletions(-) create mode 100644 src/clients/auth/index.ts create mode 100644 src/clients/auth/instance.ts create mode 100644 src/clients/auth/types.ts create mode 100644 src/handlers/account/index.ts create mode 100644 src/handlers/account/middleware.ts create mode 100644 src/handlers/account/types.ts rename src/models/{user.model.ts => account.model.ts} (53%) delete mode 100644 src/models/token.model.ts create mode 100644 src/routes/account.routes.ts create mode 100644 src/types/fastify-jwt.d.ts create mode 100644 src/types/models/account.type.ts rename src/types/models/{eloquent-model.ts => eloquent-model.type.ts} (100%) create mode 100644 src/types/models/user.type.ts create mode 100644 src/types/routes/account-routes.type.ts diff --git a/.env.example b/.env.example index 78d81f9..6ea1f71 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,10 @@ HTTP_HOST=localhost HTTP_PORT=8080 +# Auth service +AUTH_SERVICE_HOST=localhost +AUTH_SERVICE_PORT=8081 + # Database Options DB_HOST=127.0.0.1 DB_PORT=27017 diff --git a/.eslintrc b/.eslintrc index c31ebe5..e026c68 100644 --- a/.eslintrc +++ b/.eslintrc @@ -130,7 +130,7 @@ "error", { "props": true, - "ignorePropertyModificationsFor": ["accamulator"] + "ignorePropertyModificationsFor": ["accamulator", "request"] } ], "object-shorthand": "off", diff --git a/README.md b/README.md index 1a23db1..3b4541a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,13 @@ DB_PASSWORD - mongo password DB_NAME - database name ``` +**Для подключения к сервису авторизации** + +``` +AUTH_SERVICE_HOST - auth service host +AUTH_SERVICE_PORT - auth service port +``` + ## Среды окружения ``` diff --git a/package.json b/package.json index 8aafd80..781dba2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@fastify/cookie": "^8.3.0", "@fastify/cors": "^8.2.0", "@fastify/jwt": "^6.3.3", + "axios": "^1.2.1", "bcryptjs": "^2.4.3", "dotenv": "^16.0.3", "fastify": "^4.9.2", diff --git a/src/clients/auth/index.ts b/src/clients/auth/index.ts new file mode 100644 index 0000000..6db7939 --- /dev/null +++ b/src/clients/auth/index.ts @@ -0,0 +1,16 @@ +import { authService } from "./instance"; + +import type { User } from "@/types/models/user.type"; +import type { GetUserRequest } from "./types"; + +export const getUser = async (request: GetUserRequest): Promise => { + try { + const { data } = await authService.get(`/user/${request.id}`); + + return data; + } catch (nativeError) { + console.error(nativeError); + } + + return null; +}; diff --git a/src/clients/auth/instance.ts b/src/clients/auth/instance.ts new file mode 100644 index 0000000..3a3009f --- /dev/null +++ b/src/clients/auth/instance.ts @@ -0,0 +1,7 @@ +import axios from "axios"; + +import { CONFIGURATION } from "@/constants/configuration"; + +export const authService = axios.create({ + baseURL: `${CONFIGURATION.authService.host}:${CONFIGURATION.authService.port}`, +}); diff --git a/src/clients/auth/types.ts b/src/clients/auth/types.ts new file mode 100644 index 0000000..7dad5f5 --- /dev/null +++ b/src/clients/auth/types.ts @@ -0,0 +1,3 @@ +export type GetUserRequest = { + id: string; +}; diff --git a/src/constants/configuration.ts b/src/constants/configuration.ts index 274c665..924ef8d 100644 --- a/src/constants/configuration.ts +++ b/src/constants/configuration.ts @@ -11,10 +11,11 @@ export const CONFIGURATION = { database: process.env.DB_NAME || "database", }, service: { - privateAccessSecretKey: process.env.PRIVATE_ACCESS_SECRET_KEY || "", publicAccessSecretKey: process.env.PUBLIC_ACCESS_SECRET_KEY || "", - privateRefreshSecretKey: process.env.PRIVATE_REFRESH_SECRET_KEY || "", - publicRefreshSecretKey: process.env.PUBLIC_REFRESH_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; diff --git a/src/handlers/account/index.ts b/src/handlers/account/index.ts new file mode 100644 index 0000000..719fc09 --- /dev/null +++ b/src/handlers/account/index.ts @@ -0,0 +1,52 @@ +import { Types } from "mongoose"; + +import { AccountModel } from "@/models/account.model"; + +import { getUser } from "@/clients/auth"; +import { validateEmptyFields } from "@/utils/validate-empty-fields"; + +import type { FastifyReply } from "fastify"; +import type { CreateAccountRequest, GetAccountRequest } from "./types"; + +export const createAccount = async (request: CreateAccountRequest, reply: FastifyReply) => { + if (!Types.ObjectId.isValid(request.user.id)) { + reply.status(400); + return new Error("invalid user id"); + } + + const account = await AccountModel.findById(request.user.id).lean(); + + if (account) { + reply.status(409); + return new Error("account already exist"); + } + + const user = await getUser({ id: request.user.id }); + + if (!user) { + reply.status(404); + return new Error("user not found"); + } + + const createdAccount = new AccountModel({ + userId: user._id, + }); + + return createdAccount.save(); +}; + +export const getAccount = async (request: GetAccountRequest, reply: FastifyReply) => { + const [getAccountRequestBody, error] = validateEmptyFields(request.params || {}, ["userId"]); + + if (!getAccountRequestBody || error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(getAccountRequestBody.userId)) { + reply.status(400); + return new Error("invalid user id"); + } + + return AccountModel.findById(getAccountRequestBody.userId).lean(); +}; diff --git a/src/handlers/account/middleware.ts b/src/handlers/account/middleware.ts new file mode 100644 index 0000000..5188402 --- /dev/null +++ b/src/handlers/account/middleware.ts @@ -0,0 +1,19 @@ +import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction as Done } from "fastify"; + +export const verifyUser = async (request: FastifyRequest, reply: FastifyReply, done: Done) => { + try { + const { id } = await request.jwtVerify<{ id?: string }>({ onlyCookie: true }); + + if (!id) { + reply.status(401); + return reply.send("user id is empty"); + } + + request.user = { id }; + } catch (nativeError) { + reply.status(401); + return reply.send(nativeError); + } + + done(); +}; diff --git a/src/handlers/account/types.ts b/src/handlers/account/types.ts new file mode 100644 index 0000000..bd9a92a --- /dev/null +++ b/src/handlers/account/types.ts @@ -0,0 +1,6 @@ +import type { FastifyRequest } from "fastify"; + +import type { CreateAccountRoute, GetAccountRoute } from "@/types/routes/account-routes.type"; + +export type CreateAccountRequest = FastifyRequest; +export type GetAccountRequest = FastifyRequest; diff --git a/src/index.ts b/src/index.ts index 1dbdc31..8426a4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,11 @@ const server = new Server({ jwt: { secret: { public: CONFIGURATION.service.publicAccessSecretKey, - private: CONFIGURATION.service.privateAccessSecretKey, + private: "", + }, + verify: { + allowedIss: "pena-auth-service", + allowedAud: "pena", }, }, }, diff --git a/src/models/user.model.ts b/src/models/account.model.ts similarity index 53% rename from src/models/user.model.ts rename to src/models/account.model.ts index 36ecb68..00f217f 100644 --- a/src/models/user.model.ts +++ b/src/models/account.model.ts @@ -2,24 +2,17 @@ import { Schema, model, SchemaDefinition } from "mongoose"; import { eloquentModelSchema } from "./eloquent-model.schema"; -import type { User } from "@/types/models/user.type"; +import type { Account } from "@/types/models/account.type"; -const schema: SchemaDefinition = { - login: { +const schema: SchemaDefinition = { + userId: { type: String, required: true, }, - email: { - type: String, - required: true, - }, - password: { - type: String, - required: true, - }, - phoneNumber: { + nickname: { type: String, required: true, + default: "nickname", }, avatar: { type: String, @@ -34,9 +27,9 @@ const schema: SchemaDefinition = { const schemaSettings = { versionKey: false, - collection: "users", + collection: "accounts", }; -const UserSchema = new Schema(schema, schemaSettings); +const AccountSchema = new Schema(schema, schemaSettings); -export const UserModel = model("User", UserSchema); +export const AccountModel = model("Account", AccountSchema); diff --git a/src/models/eloquent-model.schema.ts b/src/models/eloquent-model.schema.ts index b6920b6..be46084 100644 --- a/src/models/eloquent-model.schema.ts +++ b/src/models/eloquent-model.schema.ts @@ -1,5 +1,5 @@ import type { SchemaDefinition } from "mongoose"; -import type { EloquentModel } from "@/types/models/eloquent-model"; +import type { EloquentModel } from "@/types/models/eloquent-model.type"; export const eloquentModelSchema: SchemaDefinition = { createdAt: { diff --git a/src/models/token.model.ts b/src/models/token.model.ts deleted file mode 100644 index d92fb79..0000000 --- a/src/models/token.model.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Schema, model, SchemaDefinition } from "mongoose"; - -import type { Token } from "@/types/models/token.type"; - -const schema: SchemaDefinition = { - userId: { - type: String, - required: true, - }, - refreshToken: { - type: String, - required: true, - }, -}; - -const schemaSettings = { - versionKey: false, - collection: "tokens", -}; - -const TokenSchema = new Schema(schema, schemaSettings); - -export const TokenModel = model("Token", TokenSchema); diff --git a/src/routes/account.routes.ts b/src/routes/account.routes.ts new file mode 100644 index 0000000..eb78322 --- /dev/null +++ b/src/routes/account.routes.ts @@ -0,0 +1,12 @@ +import { createAccount, getAccount } from "@/handlers/account"; +import { verifyUser } from "@/handlers/account/middleware"; + +import type { FastifyInstance, FastifyPluginOptions } from "fastify"; +import type { GetAccountRoute, CreateAccountRoute } from "@/types/routes/account-routes.type"; + +export const setRoleRoutes = (server: FastifyInstance, opts: T, done: () => void): void => { + server.get("/:userId", getAccount); + server.post("/", { preHandler: [verifyUser] }, createAccount); + + done(); +}; diff --git a/src/types/fastify-jwt.d.ts b/src/types/fastify-jwt.d.ts new file mode 100644 index 0000000..b1b3f96 --- /dev/null +++ b/src/types/fastify-jwt.d.ts @@ -0,0 +1,8 @@ +import "@fastify/jwt"; + +declare module "@fastify/jwt" { + interface FastifyJWT { + payload: { id: string }; + user: { id: string }; + } +} diff --git a/src/types/models/account.type.ts b/src/types/models/account.type.ts new file mode 100644 index 0000000..06e5beb --- /dev/null +++ b/src/types/models/account.type.ts @@ -0,0 +1,7 @@ +export type Account = { + userId: string; + nickname: string; + avatar: string; + role: string; + privilegies: Record; +}; diff --git a/src/types/models/eloquent-model.ts b/src/types/models/eloquent-model.type.ts similarity index 100% rename from src/types/models/eloquent-model.ts rename to src/types/models/eloquent-model.type.ts diff --git a/src/types/models/role.type.ts b/src/types/models/role.type.ts index e2eb2b1..820b8e8 100644 --- a/src/types/models/role.type.ts +++ b/src/types/models/role.type.ts @@ -1,4 +1,4 @@ -import type { EloquentModel } from "./eloquent-model"; +import type { EloquentModel } from "./eloquent-model.type"; export type Role = EloquentModel & { name: string; diff --git a/src/types/models/user.type.ts b/src/types/models/user.type.ts new file mode 100644 index 0000000..8a0457e --- /dev/null +++ b/src/types/models/user.type.ts @@ -0,0 +1,8 @@ +import type { EloquentModel } from "./eloquent-model.type"; + +export type User = EloquentModel & { + _id: string; + login: string; + email: string; + phoneNumber: string; +}; diff --git a/src/types/routes/account-routes.type.ts b/src/types/routes/account-routes.type.ts new file mode 100644 index 0000000..fe1fc9f --- /dev/null +++ b/src/types/routes/account-routes.type.ts @@ -0,0 +1,13 @@ +import type { RequestGenericInterface } from "fastify"; + +export type GetAccountRoute = RequestGenericInterface & { + Params?: { + userId?: string; + }; +}; + +export type CreateAccountRoute = RequestGenericInterface & { + Params?: { + userId?: string; + }; +}; diff --git a/yarn.lock b/yarn.lock index faa2666..04e14ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2049,6 +2049,11 @@ async-mutex@^0.3.2: dependencies: tslib "^2.3.1" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + atomic-sleep@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" @@ -2063,6 +2068,15 @@ avvio@^8.2.0: debug "^4.0.0" fastq "^1.6.1" +axios@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.1.tgz#44cf04a3c9f0c2252ebd85975361c026cb9f864a" + integrity sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-jest@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" @@ -2366,6 +2380,13 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^9.0.0: version "9.4.1" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" @@ -2530,6 +2551,11 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" @@ -3134,6 +3160,20 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -4357,6 +4397,18 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -4843,6 +4895,11 @@ proxy-addr@^2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" From 91178e700231d4792f3f8955cdcff7c461f53b26 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 20 Dec 2022 20:14:56 +0000 Subject: [PATCH 03/10] Privilegies --- src/configuration/combine-routes.ts | 4 + src/handlers/privilege/helpers.ts | 39 ++++++++ src/handlers/privilege/index.ts | 146 ++++++++++++++++++++++++++++ src/handlers/privilege/types.ts | 35 +++++++ src/models/privilege.model.ts | 50 ++++++++++ src/routes/account.routes.ts | 6 +- src/routes/privilege.routes.ts | 23 +++++ src/types/models/privilege.type.ts | 11 +++ src/utils/validate-empty-fields.ts | 19 +++- 9 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 src/handlers/privilege/helpers.ts create mode 100644 src/handlers/privilege/index.ts create mode 100644 src/handlers/privilege/types.ts create mode 100644 src/models/privilege.model.ts create mode 100644 src/routes/privilege.routes.ts create mode 100644 src/types/models/privilege.type.ts diff --git a/src/configuration/combine-routes.ts b/src/configuration/combine-routes.ts index 991c215..9936e35 100644 --- a/src/configuration/combine-routes.ts +++ b/src/configuration/combine-routes.ts @@ -1,7 +1,11 @@ +import { setAccountRoutes } from "@/routes/account.routes"; +import { setPrivilegeRoutes } from "@/routes/privilege.routes"; import { setRoleRoutes } from "@/routes/role.routes"; import type { FastifyInstance } from "fastify"; export const combineRoutes = (server: FastifyInstance): void => { server.register(setRoleRoutes, { prefix: "/role" }); + server.register(setAccountRoutes, { prefix: "/account" }); + server.register(setPrivilegeRoutes, { prefix: "/privilege" }); }; diff --git a/src/handlers/privilege/helpers.ts b/src/handlers/privilege/helpers.ts new file mode 100644 index 0000000..6aa1289 --- /dev/null +++ b/src/handlers/privilege/helpers.ts @@ -0,0 +1,39 @@ +import type { Privilege } from "@/types/models/privilege.type"; +import type { GetAllPrivilegiesFormat, 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 defineGetAllPrivilegiesFormat = (query: string): GetAllPrivilegiesFormat => { + const formats: GetAllPrivilegiesFormat[] = ["array", "map"]; + const findedFormat = formats.find((format) => query === format); + + if (!findedFormat) { + return "array"; + } + + return findedFormat; +}; + +export const convertPrivilegiesToMap = (privilegies: Privilege[]) => { + return privilegies.reduce>((accamulator, privilege) => { + if (!accamulator[privilege.serviceKey]) { + accamulator[privilege.serviceKey] = []; + } + + accamulator[privilege.serviceKey].push(privilege); + + return accamulator; + }, {}); +}; diff --git a/src/handlers/privilege/index.ts b/src/handlers/privilege/index.ts new file mode 100644 index 0000000..06a66a1 --- /dev/null +++ b/src/handlers/privilege/index.ts @@ -0,0 +1,146 @@ +import { Types } from "mongoose"; + +import { PrivilegeModel } from "@/models/privilege.model"; + +import { validateEmptyFields } from "@/utils/validate-empty-fields"; +import { convertPrivilegiesToMap, defineGetAllPrivilegiesFormat, validatePrivilege } from "./helpers"; + +import type { FastifyReply } from "fastify"; +import type { + GetAllPrivilegiesRequest, + GetServicePrivilegiesRequest, + RegisterPrivilegeRequest, + GetPrivilegeRequest, +} from "./types"; + +export const registerPrivilege = async (request: RegisterPrivilegeRequest, reply: FastifyReply) => { + const [requestBody, errorEmpty] = validateEmptyFields( + request.body || {}, + ["name", "privilegeId", "serviceKey", "type", "description", "price", "value"], + false + ); + + if (errorEmpty) { + reply.status(400); + return errorEmpty; + } + + const errorInvalid = validatePrivilege(requestBody); + + if (errorInvalid) { + reply.status(400); + return errorInvalid; + } + + const privilege = await PrivilegeModel.findOne({ privilegeId: requestBody.privilegeId }).lean(); + + if (privilege) { + 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 (request: GetAllPrivilegiesRequest, reply: FastifyReply) => { + const format = defineGetAllPrivilegiesFormat(request.query?.format || ""); + const privilegies = await PrivilegeModel.find({}).lean(); + + if (format === "array") { + return privilegies; + } + + if (format === "map" && !privilegies.length) { + return {}; + } + + if (format === "map" && privilegies.length) { + return convertPrivilegiesToMap(privilegies); + } + + reply.status(500); + return new Error("format not defined"); +}; + +export const getServicePrivilegies = async (request: GetServicePrivilegiesRequest, reply: FastifyReply) => { + const [requestParams, error] = validateEmptyFields(request.params || {}, ["serviceKey"]); + + if (error) { + reply.status(400); + return error; + } + + return PrivilegeModel.find({ serviceKey: requestParams.serviceKey }); +}; + +export const getPrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => { + const [requestParams, error] = validateEmptyFields(request.params || {}, ["id"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(requestParams.id)) { + reply.status(400); + return new Error("invalid id"); + } + + const privilege = await PrivilegeModel.findOne({ privilegeId: requestParams.id }); + + if (!privilege) { + 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", "price", "value"], + false + ); + + if (errorEmpty) { + reply.status(400); + return errorEmpty; + } + + const errorInvalid = validatePrivilege(requestBody); + + if (errorInvalid) { + reply.status(400); + return errorInvalid; + } + + const privilege = await PrivilegeModel.findOne({ privilegeId: requestBody.privilegeId }); + + if (!privilege) { + 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, + }); + + return privilege; +}; diff --git a/src/handlers/privilege/types.ts b/src/handlers/privilege/types.ts new file mode 100644 index 0000000..76e78c2 --- /dev/null +++ b/src/handlers/privilege/types.ts @@ -0,0 +1,35 @@ +import type { FastifyRequest } from "fastify"; + +export type RawPrivilege = { + name?: string; + privilegeId?: string; + serviceKey?: string; + description?: string; + type?: string; + value?: string; + price?: number; +}; + +export type GetAllPrivilegiesFormat = "array" | "map"; + +export type GetAllPrivilegiesRequest = FastifyRequest<{ + Querystring?: { + format?: GetAllPrivilegiesFormat; + }; +}>; + +export type RegisterPrivilegeRequest = FastifyRequest<{ + Body?: RawPrivilege; +}>; + +export type GetServicePrivilegiesRequest = FastifyRequest<{ + Params?: { + serviceKey?: string; + }; +}>; + +export type GetPrivilegeRequest = FastifyRequest<{ + Params?: { + id?: string; + }; +}>; diff --git a/src/models/privilege.model.ts b/src/models/privilege.model.ts new file mode 100644 index 0000000..c5226f4 --- /dev/null +++ b/src/models/privilege.model.ts @@ -0,0 +1,50 @@ +import { Schema, model, SchemaDefinition } from "mongoose"; + +import { eloquentModelSchema } from "./eloquent-model.schema"; + +import type { Privilege } from "@/types/models/privilege.type"; + +const schema: SchemaDefinition = { + name: { + type: String, + required: true, + }, + privilegeId: { + type: String, + required: true, + index: true, + }, + 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: true, + }, + ...eloquentModelSchema, +}; + +const schemaSettings = { + versionKey: false, + collection: "privilegies", +}; + +const PrivilegeSchema = new Schema(schema, schemaSettings); + +export const PrivilegeModel = model("Privilege", PrivilegeSchema); diff --git a/src/routes/account.routes.ts b/src/routes/account.routes.ts index eb78322..38c93fe 100644 --- a/src/routes/account.routes.ts +++ b/src/routes/account.routes.ts @@ -4,7 +4,11 @@ import { verifyUser } from "@/handlers/account/middleware"; import type { FastifyInstance, FastifyPluginOptions } from "fastify"; import type { GetAccountRoute, CreateAccountRoute } from "@/types/routes/account-routes.type"; -export const setRoleRoutes = (server: FastifyInstance, opts: T, done: () => void): void => { +export const setAccountRoutes = ( + server: FastifyInstance, + opts: T, + done: () => void +): void => { server.get("/:userId", getAccount); server.post("/", { preHandler: [verifyUser] }, createAccount); diff --git a/src/routes/privilege.routes.ts b/src/routes/privilege.routes.ts new file mode 100644 index 0000000..1a68679 --- /dev/null +++ b/src/routes/privilege.routes.ts @@ -0,0 +1,23 @@ +import { + registerPrivilege, + getAllPrivilegies, + getPrivilege, + getServicePrivilegies, + replacePrivilege, +} from "@/handlers/privilege"; + +import type { FastifyInstance, FastifyPluginOptions } from "fastify"; + +export const setPrivilegeRoutes = ( + server: FastifyInstance, + opts: T, + done: () => void +): void => { + server.get("/all", getAllPrivilegies); + server.get("/:id", getPrivilege); + server.get("/service/:serviceKey", getServicePrivilegies); + server.post("/", registerPrivilege); + server.put("/", replacePrivilege); + + done(); +}; diff --git a/src/types/models/privilege.type.ts b/src/types/models/privilege.type.ts new file mode 100644 index 0000000..d941ff5 --- /dev/null +++ b/src/types/models/privilege.type.ts @@ -0,0 +1,11 @@ +import type { EloquentModel } from "./eloquent-model.type"; + +export type Privilege = EloquentModel & { + name: string; + privilegeId: string; + serviceKey: string; + description: string; + type: "count" | "day" | "full"; + value: string; + price: number; +}; diff --git a/src/utils/validate-empty-fields.ts b/src/utils/validate-empty-fields.ts index a3f4031..bd72e88 100644 --- a/src/utils/validate-empty-fields.ts +++ b/src/utils/validate-empty-fields.ts @@ -2,14 +2,23 @@ import type { ObjectWithRequiredFields } from "@/types/object-with-required-fiel type KeyValue = Record; type Keys = Array; -type ValidateEmptyFields = [ObjectWithRequiredFields | undefined, Error | null]; +type ValidateEmptyFields = [ObjectWithRequiredFields, Error | null]; + +export const validateEmptyFields = ( + record: T, + keys: Keys = [], + strict = true +): ValidateEmptyFields => { + const validatedRecord = record as ObjectWithRequiredFields; -export const validateEmptyFields = (record: T, keys: Keys = []): ValidateEmptyFields => { for (const key of keys) { - if (!record[key]) { - return [undefined, new Error(`field <${key as string}> is empty`)]; + if (strict && !record[key]) { + return [validatedRecord, new Error(`field <${key as string}> is empty`)]; + } + if (!strict && (record[key] === undefined || record[key] === null)) { + return [validatedRecord, new Error(`field <${key as string}> is empty`)]; } } - return [record as ObjectWithRequiredFields, null]; + return [validatedRecord, null]; }; From 458dae5edba3dd4562a7769c4641ac1388dfa66e Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 21 Dec 2022 20:06:04 +0000 Subject: [PATCH 04/10] feat: tariff --- src/configuration/combine-routes.ts | 2 + src/handlers/{account => auth}/middleware.ts | 0 src/handlers/tariff/helpers.ts | 22 +++ src/handlers/tariff/index.ts | 125 +++++++++++++++ src/handlers/tariff/types.ts | 11 ++ src/models/account.model.ts | 4 +- src/models/eloquent-model.schema.ts | 12 +- src/models/privilege.model.ts | 6 +- src/models/role.model.ts | 4 +- src/models/tariff.model.ts | 36 +++++ src/routes/account.routes.ts | 2 +- src/routes/tariff.routes.ts | 14 ++ src/swagger.json | 154 ------------------- src/types/messages/tariff-message.type.ts | 10 ++ src/types/models/tariff.type.ts | 9 ++ src/types/object-with-possible-fields.ts | 3 + src/types/routes/tariff-routes.type.ts | 11 ++ 17 files changed, 261 insertions(+), 164 deletions(-) rename src/handlers/{account => auth}/middleware.ts (100%) create mode 100644 src/handlers/tariff/helpers.ts create mode 100644 src/handlers/tariff/index.ts create mode 100644 src/handlers/tariff/types.ts create mode 100644 src/models/tariff.model.ts create mode 100644 src/routes/tariff.routes.ts delete mode 100644 src/swagger.json create mode 100644 src/types/messages/tariff-message.type.ts create mode 100644 src/types/models/tariff.type.ts create mode 100644 src/types/object-with-possible-fields.ts create mode 100644 src/types/routes/tariff-routes.type.ts diff --git a/src/configuration/combine-routes.ts b/src/configuration/combine-routes.ts index 9936e35..ae683ad 100644 --- a/src/configuration/combine-routes.ts +++ b/src/configuration/combine-routes.ts @@ -1,6 +1,7 @@ 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 type { FastifyInstance } from "fastify"; @@ -8,4 +9,5 @@ export const combineRoutes = (server: FastifyInstance): void => { server.register(setRoleRoutes, { prefix: "/role" }); server.register(setAccountRoutes, { prefix: "/account" }); server.register(setPrivilegeRoutes, { prefix: "/privilege" }); + server.register(setTariffRoutes, { prefix: "/tariff" }); }; diff --git a/src/handlers/account/middleware.ts b/src/handlers/auth/middleware.ts similarity index 100% rename from src/handlers/account/middleware.ts rename to src/handlers/auth/middleware.ts diff --git a/src/handlers/tariff/helpers.ts b/src/handlers/tariff/helpers.ts new file mode 100644 index 0000000..b5ae578 --- /dev/null +++ b/src/handlers/tariff/helpers.ts @@ -0,0 +1,22 @@ +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, Error | null] => { + const [validatedTariff, errorEmpty] = validateEmptyFields( + tariff, + ["isCustom", "name", "price", "privilegies"], + false + ); + + if (errorEmpty) { + return [validatedTariff, errorEmpty]; + } + + if (isNaN(Number(validatedTariff.price))) { + return [validatedTariff, new Error("invalid 'price' value")]; + } + + return [validatedTariff, null]; +}; diff --git a/src/handlers/tariff/index.ts b/src/handlers/tariff/index.ts new file mode 100644 index 0000000..14ce648 --- /dev/null +++ b/src/handlers/tariff/index.ts @@ -0,0 +1,125 @@ +import { Types } from "mongoose"; + +import { TariffModel } from "@/models/tariff.model"; +import { PrivilegeModel } from "@/models/privilege.model"; + +import { validateEmptyFields } from "@/utils/validate-empty-fields"; +import { validateTariff } from "./helpers"; + +import type { FastifyReply } from "fastify"; + +import type { Privilege } from "@/types/models/privilege.type"; +import type { CreateTariffRequest, GetTariffRequest, ReplaceTariffRequest } from "./types"; + +export const getTariffs = async () => TariffModel.find({}).lean(); + +export const getTariff = async (request: GetTariffRequest, reply: FastifyReply) => { + const [requestParams, error] = validateEmptyFields(request.params || {}, ["id"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(requestParams.id)) { + reply.status(400); + return new Error("invalid id"); + } + + const tariff = await TariffModel.findById(requestParams.id).lean(); + + if (!tariff) { + reply.status(404); + return new Error("tariff not found"); + } + + return tariff; +}; + +export const createTariff = async (request: CreateTariffRequest, reply: FastifyReply) => { + const [requestBody, error] = validateTariff(request.body || {}); + + if (error) { + reply.status(400); + return error; + } + + for (const privilege of requestBody.privilegies) { + const findedPrivilege = await PrivilegeModel.findOne({ privilegeId: privilege.privilegeId }).lean(); + + if (!findedPrivilege) { + reply.status(404); + return new Error(`privilege with id ${privilege.privilegeId} not found`); + } + } + + const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { + if (!accamulator[privilege.privilegeId]) { + accamulator[privilege.privilegeId] = []; + } + + accamulator[privilege.privilegeId].push(privilege); + + return accamulator; + }, {}); + + const newTariff = new TariffModel({ + name: requestBody.name, + price: requestBody.price, + isCustom: requestBody.isCustom, + privilegies: privilegiesMap, + }); + + await newTariff.save(); + + return newTariff; +}; + +export const replaceTariff = async (request: ReplaceTariffRequest, reply: FastifyReply) => { + const [requestBody, error] = validateTariff(request.body || {}); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(request.params?.id || "")) { + reply.status(400); + return new Error("invalid id"); + } + + const tariff = await TariffModel.findById(request.params?.id); + + if (!tariff) { + reply.status(404); + return new Error("tariff not found"); + } + + for (const privilege of requestBody.privilegies) { + const findedPrivilege = await PrivilegeModel.findOne({ privilegeId: privilege.privilegeId }).lean(); + + if (!findedPrivilege) { + reply.status(404); + return new Error(`privilege with id ${privilege.privilegeId} not found`); + } + } + + const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { + if (!accamulator[privilege.privilegeId]) { + accamulator[privilege.privilegeId] = []; + } + + accamulator[privilege.privilegeId].push(privilege); + + return accamulator; + }, {}); + + await tariff.replaceOne({ + name: requestBody.name, + price: requestBody.price, + isCustom: requestBody.isCustom, + privilegies: privilegiesMap, + }); + + return tariff; +}; diff --git a/src/handlers/tariff/types.ts b/src/handlers/tariff/types.ts new file mode 100644 index 0000000..2eab2ed --- /dev/null +++ b/src/handlers/tariff/types.ts @@ -0,0 +1,11 @@ +import type { FastifyRequest } from "fastify"; +import type { CreateTariffRoute, ReplaceTariffRoute } from "@/types/routes/tariff-routes.type"; + +export type GetTariffRequest = FastifyRequest<{ + Params?: { + id?: string; + }; +}>; + +export type CreateTariffRequest = FastifyRequest; +export type ReplaceTariffRequest = FastifyRequest; diff --git a/src/models/account.model.ts b/src/models/account.model.ts index 00f217f..7c2820f 100644 --- a/src/models/account.model.ts +++ b/src/models/account.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { eloquentModelSchema } from "./eloquent-model.schema"; +import { EloquentSchema } from "./eloquent-model.schema"; import type { Account } from "@/types/models/account.type"; @@ -22,7 +22,7 @@ const schema: SchemaDefinition = { type: String, default: "user", }, - ...eloquentModelSchema, + ...EloquentSchema, }; const schemaSettings = { diff --git a/src/models/eloquent-model.schema.ts b/src/models/eloquent-model.schema.ts index be46084..832bd37 100644 --- a/src/models/eloquent-model.schema.ts +++ b/src/models/eloquent-model.schema.ts @@ -1,7 +1,8 @@ -import type { SchemaDefinition } from "mongoose"; +import { Schema, SchemaDefinition } from "mongoose"; + import type { EloquentModel } from "@/types/models/eloquent-model.type"; -export const eloquentModelSchema: SchemaDefinition = { +export const schema: SchemaDefinition = { createdAt: { type: Date, required: true, @@ -22,3 +23,10 @@ export const eloquentModelSchema: SchemaDefinition = { default: false, }, }; + +const schemaSettings = { + versionKey: false, + collection: "privilegies", +}; + +export const EloquentSchema = new Schema(schema, schemaSettings); diff --git a/src/models/privilege.model.ts b/src/models/privilege.model.ts index c5226f4..fb7180a 100644 --- a/src/models/privilege.model.ts +++ b/src/models/privilege.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { eloquentModelSchema } from "./eloquent-model.schema"; +import { EloquentSchema } from "./eloquent-model.schema"; import type { Privilege } from "@/types/models/privilege.type"; @@ -37,7 +37,7 @@ const schema: SchemaDefinition = { type: Number, required: true, }, - ...eloquentModelSchema, + ...EloquentSchema, }; const schemaSettings = { @@ -45,6 +45,6 @@ const schemaSettings = { collection: "privilegies", }; -const PrivilegeSchema = new Schema(schema, schemaSettings); +export const PrivilegeSchema = new Schema(schema, schemaSettings); export const PrivilegeModel = model("Privilege", PrivilegeSchema); diff --git a/src/models/role.model.ts b/src/models/role.model.ts index fb060d3..188ddb5 100644 --- a/src/models/role.model.ts +++ b/src/models/role.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { eloquentModelSchema } from "./eloquent-model.schema"; +import { EloquentSchema } from "./eloquent-model.schema"; import type { Role } from "@/types/models/role.type"; @@ -14,7 +14,7 @@ const schema: SchemaDefinition = { of: Boolean, default: {}, }, - ...eloquentModelSchema, + ...EloquentSchema, }; const schemaSettings = { diff --git a/src/models/tariff.model.ts b/src/models/tariff.model.ts new file mode 100644 index 0000000..219fe04 --- /dev/null +++ b/src/models/tariff.model.ts @@ -0,0 +1,36 @@ +import { Schema, model, SchemaDefinition } from "mongoose"; + +import { EloquentSchema } from "./eloquent-model.schema"; +import { PrivilegeSchema } from "./privilege.model"; + +import type { Tariff } from "@/types/models/tariff.type"; + +const schema: SchemaDefinition = { + name: { + type: String, + required: true, + }, + price: { + type: Number, + required: true, + }, + isCustom: { + type: Boolean, + default: false, + }, + privilegies: { + type: Map, + of: PrivilegeSchema, + default: {}, + }, + ...EloquentSchema, +}; + +const schemaSettings = { + versionKey: false, + collection: "tariffs", +}; + +const TariffSchema = new Schema(schema, schemaSettings); + +export const TariffModel = model("Tariff", TariffSchema); diff --git a/src/routes/account.routes.ts b/src/routes/account.routes.ts index 38c93fe..4d5852d 100644 --- a/src/routes/account.routes.ts +++ b/src/routes/account.routes.ts @@ -1,5 +1,5 @@ import { createAccount, getAccount } from "@/handlers/account"; -import { verifyUser } from "@/handlers/account/middleware"; +import { verifyUser } from "@/handlers/auth/middleware"; import type { FastifyInstance, FastifyPluginOptions } from "fastify"; import type { GetAccountRoute, CreateAccountRoute } from "@/types/routes/account-routes.type"; diff --git a/src/routes/tariff.routes.ts b/src/routes/tariff.routes.ts new file mode 100644 index 0000000..85ea1fb --- /dev/null +++ b/src/routes/tariff.routes.ts @@ -0,0 +1,14 @@ +import { createTariff, replaceTariff, getTariff, getTariffs } from "@/handlers/tariff"; +import { verifyUser } from "@/handlers/auth/middleware"; + +import type { FastifyInstance, FastifyPluginOptions } from "fastify"; +import type { CreateTariffRoute, ReplaceTariffRoute } from "@/types/routes/tariff-routes.type"; + +export const setTariffRoutes = (server: FastifyInstance, opts: T, done: () => void): void => { + server.get("/:id", getTariff); + server.get("/", getTariffs); + server.post("/", { preHandler: [verifyUser] }, createTariff); + server.put("/", { preHandler: [verifyUser] }, replaceTariff); + + done(); +}; diff --git a/src/swagger.json b/src/swagger.json deleted file mode 100644 index 46ed610..0000000 --- a/src/swagger.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "PenaHub Server", - "version": "1.0.11", - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - } - }, - "externalDocs": { - "description": "Find out more about Swagger", - "url": "http://swagger.io" - }, - "servers": [ - { - "url": "http://84.201.155.77:8080" - } - ], - "paths": { - "/": { - "get": { - "tags": [ - "/" - ], - "summary": "Список пользователей", - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/auth": { - "get": { - "tags": [ - "auth" - ], - "summary": "Страница регистрации", - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/api/v1/auth": { - "post": { - "tags": [ - "auth" - ], - "summary": "Регистрация нового пользователя", - "requestBody": { - "content": { - "application/urlencoded": { - "schema": { - "type": "object", - "properties": { - "login": { - "type": "string", - "example": "valera24" - }, - "email": { - "type": "string", - "example": "valera24@mail.com" - }, - "phoneNumber": { - "type": "string", - "example": 79993337733 - }, - "password": { - "type": "string", - "example": "secret" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/login": { - "get": { - "tags": [ - "login" - ], - "summary": "Страница авторизации", - "parameters": [ - { - "name": "return", - "in": "query", - "description": "URL на который будет перенаправлен пользователь после авторизации", - "required": false - } - ], - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/api/v1/login": { - "post": { - "tags": [ - "login" - ], - "summary": "Авторизация пользователя", - "requestBody": { - "content": { - "application/urlencoded": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "valera24@mail.com" - }, - "password": { - "type": "string", - "example": "secret" - }, - "goto": { - "type": "string", - "example": "http://penahub.quiz/signedup" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful (Если не передан goto)" - }, - "304": { - "description": "Successful (Если передан goto)" - }, - "401": { - "description": "password incorrect" - }, - "404": { - "description": "User not found" - } - } - } - } - } - } \ No newline at end of file diff --git a/src/types/messages/tariff-message.type.ts b/src/types/messages/tariff-message.type.ts new file mode 100644 index 0000000..e420035 --- /dev/null +++ b/src/types/messages/tariff-message.type.ts @@ -0,0 +1,10 @@ +import type { EloquentModel } from "../models/eloquent-model.type"; +import type { Privilege } from "../models/privilege.type"; +import type { Tariff } from "../models/tariff.type"; +import type { ObjectWithPossibleFields } from "../object-with-possible-fields"; + +export type TariffMessage = ObjectWithPossibleFields< + Omit & { + privilegies: Privilege[]; + } +>; diff --git a/src/types/models/tariff.type.ts b/src/types/models/tariff.type.ts new file mode 100644 index 0000000..67120e0 --- /dev/null +++ b/src/types/models/tariff.type.ts @@ -0,0 +1,9 @@ +import type { Privilege } from "./privilege.type"; +import type { EloquentModel } from "./eloquent-model.type"; + +export type Tariff = EloquentModel & { + name: string; + price: number; + isCustom: boolean; + privilegies: Record; +}; diff --git a/src/types/object-with-possible-fields.ts b/src/types/object-with-possible-fields.ts new file mode 100644 index 0000000..45b616b --- /dev/null +++ b/src/types/object-with-possible-fields.ts @@ -0,0 +1,3 @@ +export type ObjectWithPossibleFields> = { + [Key in keyof KeyValue]?: KeyValue[Key]; +}; diff --git a/src/types/routes/tariff-routes.type.ts b/src/types/routes/tariff-routes.type.ts new file mode 100644 index 0000000..042e14b --- /dev/null +++ b/src/types/routes/tariff-routes.type.ts @@ -0,0 +1,11 @@ +import type { RequestGenericInterface } from "fastify"; +import type { TariffMessage } from "../messages/tariff-message.type"; + +export type CreateTariffRoute = RequestGenericInterface & { + Body?: TariffMessage; +}; + +export type ReplaceTariffRoute = RequestGenericInterface & { + Body?: TariffMessage; + Params?: { id?: string }; +}; From 8743492bc2e47a77def0d897f61389d6ef05eedd Mon Sep 17 00:00:00 2001 From: Kirill Date: Thu, 22 Dec 2022 10:45:44 +0000 Subject: [PATCH 05/10] feat: swagger --- README.md | 36 +-- docs/environment/README.md | 35 +++ package.json | 2 + src/configuration/combine-routes.ts | 14 +- src/configuration/configure-env.ts | 15 +- src/configuration/define-environment.ts | 14 ++ src/configuration/register-fastify-plugins.ts | 30 +++ src/handlers/account/index.ts | 6 +- src/handlers/account/types.ts | 9 +- src/handlers/auth/middleware.ts | 2 +- src/handlers/privilege/helpers.ts | 6 +- src/handlers/privilege/index.ts | 3 +- src/handlers/roles/index.ts | 33 ++- src/handlers/roles/types.ts | 3 - src/handlers/tariff/index.ts | 8 +- src/handlers/tariff/types.ts | 12 +- src/index.ts | 2 +- src/models/account.model.ts | 4 +- src/models/eloquent-model.schema.ts | 32 --- src/models/eloquent.schema.ts | 25 +++ src/models/privilege.model.ts | 4 +- src/models/role.model.ts | 4 +- src/models/tariff.model.ts | 4 +- src/plugins/print-routes.ts | 7 +- src/routes/account.routes.ts | 17 +- src/routes/privilege.routes.ts | 28 +-- src/routes/role.routes.ts | 33 ++- src/routes/tariff.routes.ts | 17 +- src/server/index.ts | 9 +- src/server/router.ts | 77 +++++++ src/swagger/account/index.ts | 19 ++ src/swagger/account/inputs.ts | 11 + src/swagger/account/models.ts | 46 ++++ src/swagger/account/responses.ts | 18 ++ src/swagger/privilege/index.ts | 46 ++++ src/swagger/privilege/inputs.ts | 62 ++++++ src/swagger/privilege/models.ts | 103 +++++++++ src/swagger/privilege/responses.ts | 35 +++ src/swagger/role/index.ts | 71 ++++++ src/swagger/role/inputs.ts | 58 +++++ src/swagger/role/models.ts | 29 +++ src/swagger/role/responses.ts | 50 +++++ src/swagger/tariff/index.ts | 30 +++ src/swagger/tariff/inputs.ts | 48 ++++ src/swagger/tariff/models.ts | 94 ++++++++ src/swagger/tariff/responses.ts | 30 +++ src/types/messages/tariff-message.type.ts | 4 +- src/types/models/account.type.ts | 5 +- ...loquent-model.type.ts => eloquent.type.ts} | 2 +- src/types/models/privilege.type.ts | 4 +- src/types/models/role.type.ts | 4 +- src/types/models/tariff.type.ts | 6 +- src/types/models/user.type.ts | 4 +- src/types/routes/account-routes.type.ts | 13 -- src/types/routes/tariff-routes.type.ts | 11 - src/types/swagger.type.ts | 48 ++++ src/utils/swagger-error.ts | 19 ++ yarn.lock | 208 +++++++++++++++++- 58 files changed, 1336 insertions(+), 233 deletions(-) create mode 100644 docs/environment/README.md create mode 100644 src/configuration/define-environment.ts delete mode 100644 src/models/eloquent-model.schema.ts create mode 100644 src/models/eloquent.schema.ts create mode 100644 src/server/router.ts create mode 100644 src/swagger/account/index.ts create mode 100644 src/swagger/account/inputs.ts create mode 100644 src/swagger/account/models.ts create mode 100644 src/swagger/account/responses.ts create mode 100644 src/swagger/privilege/index.ts create mode 100644 src/swagger/privilege/inputs.ts create mode 100644 src/swagger/privilege/models.ts create mode 100644 src/swagger/privilege/responses.ts create mode 100644 src/swagger/role/index.ts create mode 100644 src/swagger/role/inputs.ts create mode 100644 src/swagger/role/models.ts create mode 100644 src/swagger/role/responses.ts create mode 100644 src/swagger/tariff/index.ts create mode 100644 src/swagger/tariff/inputs.ts create mode 100644 src/swagger/tariff/models.ts create mode 100644 src/swagger/tariff/responses.ts rename src/types/models/{eloquent-model.type.ts => eloquent.type.ts} (73%) delete mode 100644 src/types/routes/account-routes.type.ts delete mode 100644 src/types/routes/tariff-routes.type.ts create mode 100644 src/types/swagger.type.ts create mode 100644 src/utils/swagger-error.ts diff --git a/README.md b/README.md index 3b4541a..edd977d 100644 --- a/README.md +++ b/README.md @@ -7,38 +7,6 @@ 2) yarn dev - запуск проекта в режиме разработки ``` -## Переменные окружения сервиса: +# Ссылки на другую документацию: -**Для конфигурации сервера**: - -``` -ENVIRONMENT - application environment -HTTP_HOST - service host -HTTP_PORT - service port -PUBLIC_ACCESS_SECRET_KEY - secret to verify private access secret key -``` - -**Для конфигурации базы данных**: - -``` -DB_HOST - mongo host -DB_PORT - mongo port -DB_USERNAME - mongo username -DB_PASSWORD - mongo password -DB_NAME - database name -``` - -**Для подключения к сервису авторизации** - -``` -AUTH_SERVICE_HOST - auth service host -AUTH_SERVICE_PORT - auth service port -``` - -## Среды окружения - -``` -development - среда для разработки -staging - среда для тестирования продукта -production - среда продакшена -``` +- [**Переменные окружения сервиса**](./docs/environment/README.md) diff --git a/docs/environment/README.md b/docs/environment/README.md new file mode 100644 index 0000000..afc4b90 --- /dev/null +++ b/docs/environment/README.md @@ -0,0 +1,35 @@ +# Переменные окружения сервиса: + +**Для конфигурации сервера**: + +``` +ENVIRONMENT - application environment +HTTP_HOST - service host +HTTP_PORT - service port +PUBLIC_ACCESS_SECRET_KEY - secret to verify private access secret key +``` + +**Для конфигурации базы данных**: + +``` +DB_HOST - mongo host +DB_PORT - mongo port +DB_USERNAME - mongo username +DB_PASSWORD - mongo password +DB_NAME - database name +``` + +**Для подключения к сервису авторизации** + +``` +AUTH_SERVICE_HOST - auth service host +AUTH_SERVICE_PORT - auth service port +``` + +## Среды окружения + +``` +development - среда для разработки +staging - среда для тестирования продукта +production - среда продакшена +``` diff --git a/package.json b/package.json index 781dba2..dfc1c85 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "devDependencies": { "@commitlint/cli": "^17.1.2", "@commitlint/config-conventional": "^17.1.0", + "@fastify/swagger": "^8.2.1", + "@fastify/swagger-ui": "^1.3.0", "@types/bcryptjs": "^2.4.2", "@types/jest": "^29.2.3", "@types/jsonwebtoken": "^8.5.9", diff --git a/src/configuration/combine-routes.ts b/src/configuration/combine-routes.ts index ae683ad..7d242f6 100644 --- a/src/configuration/combine-routes.ts +++ b/src/configuration/combine-routes.ts @@ -1,13 +1,13 @@ +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 type { FastifyInstance } from "fastify"; - -export const combineRoutes = (server: FastifyInstance): void => { - server.register(setRoleRoutes, { prefix: "/role" }); - server.register(setAccountRoutes, { prefix: "/account" }); - server.register(setPrivilegeRoutes, { prefix: "/privilege" }); - server.register(setTariffRoutes, { prefix: "/tariff" }); +export const combineRoutes = (router: Router): void => { + router.group("/role", setRoleRoutes); + router.group("/account", setAccountRoutes); + router.group("/privilege", setPrivilegeRoutes); + router.group("/tariff", setTariffRoutes); }; diff --git a/src/configuration/configure-env.ts b/src/configuration/configure-env.ts index 3c23477..3240e5f 100644 --- a/src/configuration/configure-env.ts +++ b/src/configuration/configure-env.ts @@ -1,20 +1,7 @@ import path from "path"; import dotenv from "dotenv"; -import { DEFAULT } from "@/constants/default"; - -import type { Environment } from "@/types/environment"; - -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 DEFAULT.environment; -}; +import { defineEnvironment } from "./define-environment"; export const configureENV = () => { const environment = defineEnvironment(); diff --git a/src/configuration/define-environment.ts b/src/configuration/define-environment.ts new file mode 100644 index 0000000..58b8e59 --- /dev/null +++ b/src/configuration/define-environment.ts @@ -0,0 +1,14 @@ +import { DEFAULT } from "@/constants/default"; + +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 DEFAULT.environment; +}; diff --git a/src/configuration/register-fastify-plugins.ts b/src/configuration/register-fastify-plugins.ts index b6d405d..696d72a 100644 --- a/src/configuration/register-fastify-plugins.ts +++ b/src/configuration/register-fastify-plugins.ts @@ -1,8 +1,11 @@ import cors from "@fastify/cors"; 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 { CONFIGURATION } from "@/constants/configuration"; import type { FastifyInstance } from "fastify"; import type { PluginsOptions } from "@/types/configuration/plugins-options"; @@ -11,6 +14,33 @@ export const registerFastifyPlugins = (fastify: FastifyInstance, options: Plugin fastify.register(cors, options.cors); fastify.register(cookie, options.cookie); fastify.register(jwt, options.jwt); + fastify.register(swagger, { + openapi: { + info: { + title: "Hub admin backend", + description: "Тестирование сервиса админ панели хаба", + version: "0.1.0", + }, + servers: [{ url: `http://${CONFIGURATION.http.host || "localhost"}:${CONFIGURATION.http.port}` }], + components: { + securitySchemes: { + bearer: { + type: "http", + bearerFormat: "JWT", + scheme: "bearer", + }, + }, + }, + }, + hideUntagged: true, + }); + fastify.register(swaggerUI, { + routePrefix: "/swagger", + uiConfig: { + docExpansion: "full", + deepLinking: false, + }, + }); fastify.register(printRoutes); return fastify; diff --git a/src/handlers/account/index.ts b/src/handlers/account/index.ts index 719fc09..febfa41 100644 --- a/src/handlers/account/index.ts +++ b/src/handlers/account/index.ts @@ -5,10 +5,10 @@ import { AccountModel } from "@/models/account.model"; import { getUser } from "@/clients/auth"; import { validateEmptyFields } from "@/utils/validate-empty-fields"; -import type { FastifyReply } from "fastify"; -import type { CreateAccountRequest, GetAccountRequest } from "./types"; +import type { FastifyReply, FastifyRequest } from "fastify"; +import type { GetAccountRequest } from "./types"; -export const createAccount = async (request: CreateAccountRequest, reply: FastifyReply) => { +export const createAccount = async (request: FastifyRequest, reply: FastifyReply) => { if (!Types.ObjectId.isValid(request.user.id)) { reply.status(400); return new Error("invalid user id"); diff --git a/src/handlers/account/types.ts b/src/handlers/account/types.ts index bd9a92a..1cd05c0 100644 --- a/src/handlers/account/types.ts +++ b/src/handlers/account/types.ts @@ -1,6 +1,7 @@ import type { FastifyRequest } from "fastify"; -import type { CreateAccountRoute, GetAccountRoute } from "@/types/routes/account-routes.type"; - -export type CreateAccountRequest = FastifyRequest; -export type GetAccountRequest = FastifyRequest; +export type GetAccountRequest = FastifyRequest<{ + Params?: { + userId?: string; + }; +}>; diff --git a/src/handlers/auth/middleware.ts b/src/handlers/auth/middleware.ts index 5188402..c5119ea 100644 --- a/src/handlers/auth/middleware.ts +++ b/src/handlers/auth/middleware.ts @@ -2,7 +2,7 @@ import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction as Done } fr export const verifyUser = async (request: FastifyRequest, reply: FastifyReply, done: Done) => { try { - const { id } = await request.jwtVerify<{ id?: string }>({ onlyCookie: true }); + const { id } = await request.jwtVerify<{ id?: string }>(); if (!id) { reply.status(401); diff --git a/src/handlers/privilege/helpers.ts b/src/handlers/privilege/helpers.ts index 6aa1289..b5d95d3 100644 --- a/src/handlers/privilege/helpers.ts +++ b/src/handlers/privilege/helpers.ts @@ -15,7 +15,11 @@ export const validatePrivilege = (privilege: RawPrivilege): Error | null => { return null; }; -export const defineGetAllPrivilegiesFormat = (query: string): GetAllPrivilegiesFormat => { +export const defineGetAllPrivilegiesFormat = (query?: string): GetAllPrivilegiesFormat => { + if (!query) { + return "array"; + } + const formats: GetAllPrivilegiesFormat[] = ["array", "map"]; const findedFormat = formats.find((format) => query === format); diff --git a/src/handlers/privilege/index.ts b/src/handlers/privilege/index.ts index 06a66a1..2193a59 100644 --- a/src/handlers/privilege/index.ts +++ b/src/handlers/privilege/index.ts @@ -53,7 +53,7 @@ export const registerPrivilege = async (request: RegisterPrivilegeRequest, reply }; export const getAllPrivilegies = async (request: GetAllPrivilegiesRequest, reply: FastifyReply) => { - const format = defineGetAllPrivilegiesFormat(request.query?.format || ""); + const format = defineGetAllPrivilegiesFormat(request.query?.format); const privilegies = await PrivilegeModel.find({}).lean(); if (format === "array") { @@ -69,6 +69,7 @@ export const getAllPrivilegies = async (request: GetAllPrivilegiesRequest, reply } reply.status(500); + return new Error("format not defined"); }; diff --git a/src/handlers/roles/index.ts b/src/handlers/roles/index.ts index 028774d..def0c9d 100644 --- a/src/handlers/roles/index.ts +++ b/src/handlers/roles/index.ts @@ -73,33 +73,32 @@ export const removeRole = async (request: RemoveRoleRequest, reply: FastifyReply }; export const getRole = async (request: GetRoleRequest, reply: FastifyReply) => { - const { name, id } = request.body || {}; const { query } = request.params || {}; - if (!name && !id && !query) { - reply.status(400); - return new Error("either name or id must be filled"); - } - - if (name || id) { - return RoleModel.findOne({ - $or: [ - { name, isDeleted: false }, - { id, isDeleted: false }, - ], - }).lean(); - } - if (!query) { reply.status(400); return new Error("query is empty"); } if (Types.ObjectId.isValid(query)) { - return RoleModel.findOne({ _id: new Types.ObjectId(query), isDeleted: false }).lean(); + const role = await RoleModel.findOne({ _id: new Types.ObjectId(query), isDeleted: false }).lean(); + + if (!role) { + reply.status(404); + return new Error("role not found"); + } + + return role; } - return RoleModel.findOne({ name: query, isDeleted: false }).lean(); + const role = await RoleModel.findOne({ name: query, isDeleted: false }).lean(); + + if (!role) { + reply.status(404); + return new Error("role not found"); + } + + return role; }; export const replaceRole = async (request: UpdateRoleRequest, reply: FastifyReply) => { diff --git a/src/handlers/roles/types.ts b/src/handlers/roles/types.ts index f5dae60..a9fda7e 100644 --- a/src/handlers/roles/types.ts +++ b/src/handlers/roles/types.ts @@ -9,9 +9,6 @@ export type CreateRoleRequest = FastifyRequest<{ Body: RoleRequest }>; export type RemoveRoleRequest = FastifyRequest<{ Body: { id?: string } }>; export type GetRoleRequest = FastifyRequest<{ - Body?: Pick & { - id?: string; - }; Params: { query?: string; }; diff --git a/src/handlers/tariff/index.ts b/src/handlers/tariff/index.ts index 14ce648..dae2b8f 100644 --- a/src/handlers/tariff/index.ts +++ b/src/handlers/tariff/index.ts @@ -53,12 +53,8 @@ export const createTariff = async (request: CreateTariffRequest, reply: FastifyR } } - const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { - if (!accamulator[privilege.privilegeId]) { - accamulator[privilege.privilegeId] = []; - } - - accamulator[privilege.privilegeId].push(privilege); + const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { + accamulator[privilege.privilegeId] = privilege; return accamulator; }, {}); diff --git a/src/handlers/tariff/types.ts b/src/handlers/tariff/types.ts index 2eab2ed..b2f73c1 100644 --- a/src/handlers/tariff/types.ts +++ b/src/handlers/tariff/types.ts @@ -1,5 +1,5 @@ import type { FastifyRequest } from "fastify"; -import type { CreateTariffRoute, ReplaceTariffRoute } from "@/types/routes/tariff-routes.type"; +import type { TariffMessage } from "@/types/messages/tariff-message.type"; export type GetTariffRequest = FastifyRequest<{ Params?: { @@ -7,5 +7,11 @@ export type GetTariffRequest = FastifyRequest<{ }; }>; -export type CreateTariffRequest = FastifyRequest; -export type ReplaceTariffRequest = FastifyRequest; +export type CreateTariffRequest = FastifyRequest<{ + Body?: TariffMessage; +}>; + +export type ReplaceTariffRequest = FastifyRequest<{ + Body?: TariffMessage; + Params?: { id?: string }; +}>; diff --git a/src/index.ts b/src/index.ts index 8426a4d..5a30e29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ const server = new Server({ jwt: { secret: { public: CONFIGURATION.service.publicAccessSecretKey, - private: "", + private: "secret", }, verify: { allowedIss: "pena-auth-service", diff --git a/src/models/account.model.ts b/src/models/account.model.ts index 7c2820f..c6c8b16 100644 --- a/src/models/account.model.ts +++ b/src/models/account.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { EloquentSchema } from "./eloquent-model.schema"; +import { eloquentSchema } from "./eloquent.schema"; import type { Account } from "@/types/models/account.type"; @@ -22,7 +22,7 @@ const schema: SchemaDefinition = { type: String, default: "user", }, - ...EloquentSchema, + ...eloquentSchema, }; const schemaSettings = { diff --git a/src/models/eloquent-model.schema.ts b/src/models/eloquent-model.schema.ts deleted file mode 100644 index 832bd37..0000000 --- a/src/models/eloquent-model.schema.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Schema, SchemaDefinition } from "mongoose"; - -import type { EloquentModel } from "@/types/models/eloquent-model.type"; - -export const schema: SchemaDefinition = { - 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, - }, -}; - -const schemaSettings = { - versionKey: false, - collection: "privilegies", -}; - -export const EloquentSchema = new Schema(schema, schemaSettings); diff --git a/src/models/eloquent.schema.ts b/src/models/eloquent.schema.ts new file mode 100644 index 0000000..06ace40 --- /dev/null +++ b/src/models/eloquent.schema.ts @@ -0,0 +1,25 @@ +import { SchemaDefinition } from "mongoose"; + +import type { Eloquent } from "@/types/models/eloquent.type"; + +export const eloquentSchema: SchemaDefinition = { + 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, + }, +}; diff --git a/src/models/privilege.model.ts b/src/models/privilege.model.ts index fb7180a..d1ace2e 100644 --- a/src/models/privilege.model.ts +++ b/src/models/privilege.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { EloquentSchema } from "./eloquent-model.schema"; +import { eloquentSchema } from "./eloquent.schema"; import type { Privilege } from "@/types/models/privilege.type"; @@ -37,7 +37,7 @@ const schema: SchemaDefinition = { type: Number, required: true, }, - ...EloquentSchema, + ...eloquentSchema, }; const schemaSettings = { diff --git a/src/models/role.model.ts b/src/models/role.model.ts index 188ddb5..7064ea1 100644 --- a/src/models/role.model.ts +++ b/src/models/role.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { EloquentSchema } from "./eloquent-model.schema"; +import { eloquentSchema } from "./eloquent.schema"; import type { Role } from "@/types/models/role.type"; @@ -14,7 +14,7 @@ const schema: SchemaDefinition = { of: Boolean, default: {}, }, - ...EloquentSchema, + ...eloquentSchema, }; const schemaSettings = { diff --git a/src/models/tariff.model.ts b/src/models/tariff.model.ts index 219fe04..a1257ba 100644 --- a/src/models/tariff.model.ts +++ b/src/models/tariff.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { EloquentSchema } from "./eloquent-model.schema"; +import { eloquentSchema } from "./eloquent.schema"; import { PrivilegeSchema } from "./privilege.model"; import type { Tariff } from "@/types/models/tariff.type"; @@ -23,7 +23,7 @@ const schema: SchemaDefinition = { of: PrivilegeSchema, default: {}, }, - ...EloquentSchema, + ...eloquentSchema, }; const schemaSettings = { diff --git a/src/plugins/print-routes.ts b/src/plugins/print-routes.ts index 78b6a02..15499de 100644 --- a/src/plugins/print-routes.ts +++ b/src/plugins/print-routes.ts @@ -8,7 +8,7 @@ type PrintRoutesRouteOptions = { const METHODS_ORDER = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS"]; -const printRoutes = (routes: Array = []) => { +const printRoutes = (routes: Array = [], isSwagger = false) => { if (routes.length === 0) { return; } @@ -21,8 +21,9 @@ const printRoutes = (routes: Array = []) return accamulator + `${methodsValue}\t${route.url}\n`; }, ""); + const swaggerOutput = isSwagger ? "GET\t/swagger\n" : ""; - console.info(`\n\nAvailable routes:\n${output}`); + console.info(`\n\nAvailable routes:\n${output + swaggerOutput}`); }; export default fastifyPlugin( @@ -34,7 +35,7 @@ export default fastifyPlugin( }); instance.addHook("onReady", (next) => { - printRoutes(routes); + printRoutes(routes, !!instance.swagger); next(); }); diff --git a/src/routes/account.routes.ts b/src/routes/account.routes.ts index 4d5852d..4999865 100644 --- a/src/routes/account.routes.ts +++ b/src/routes/account.routes.ts @@ -1,16 +1,11 @@ +import { Router } from "@/server/router"; + import { createAccount, getAccount } from "@/handlers/account"; import { verifyUser } from "@/handlers/auth/middleware"; -import type { FastifyInstance, FastifyPluginOptions } from "fastify"; -import type { GetAccountRoute, CreateAccountRoute } from "@/types/routes/account-routes.type"; +import { createAccountSchema, getAccountSchema } from "@/swagger/account"; -export const setAccountRoutes = ( - server: FastifyInstance, - opts: T, - done: () => void -): void => { - server.get("/:userId", getAccount); - server.post("/", { preHandler: [verifyUser] }, createAccount); - - done(); +export const setAccountRoutes = (router: Router): void => { + router.get("/:userId", getAccount, { schema: getAccountSchema }); + router.post("/", createAccount, { preHandler: [verifyUser], schema: createAccountSchema }); }; diff --git a/src/routes/privilege.routes.ts b/src/routes/privilege.routes.ts index 1a68679..2726088 100644 --- a/src/routes/privilege.routes.ts +++ b/src/routes/privilege.routes.ts @@ -1,3 +1,5 @@ +import { Router } from "@/server/router"; + import { registerPrivilege, getAllPrivilegies, @@ -6,18 +8,18 @@ import { replacePrivilege, } from "@/handlers/privilege"; -import type { FastifyInstance, FastifyPluginOptions } from "fastify"; +import { + getPrivilegiesSchema, + getPrivilegeSchema, + getServicePrivilegiesSchema, + registerPrivilegeSchema, + replacePrivilegeSchema, +} from "@/swagger/privilege"; -export const setPrivilegeRoutes = ( - server: FastifyInstance, - opts: T, - done: () => void -): void => { - server.get("/all", getAllPrivilegies); - server.get("/:id", getPrivilege); - server.get("/service/:serviceKey", getServicePrivilegies); - server.post("/", registerPrivilege); - server.put("/", replacePrivilege); - - done(); +export const setPrivilegeRoutes = (router: Router): void => { + router.get("/", getAllPrivilegies, { schema: getPrivilegiesSchema }); + router.get("/:id", getPrivilege, { schema: getPrivilegeSchema }); + router.get("/service/:serviceKey", getServicePrivilegies, { schema: getServicePrivilegiesSchema }); + router.post("/", registerPrivilege, { schema: registerPrivilegeSchema }); + router.put("/", replacePrivilege, { schema: replacePrivilegeSchema }); }; diff --git a/src/routes/role.routes.ts b/src/routes/role.routes.ts index 40042cb..36dfe49 100644 --- a/src/routes/role.routes.ts +++ b/src/routes/role.routes.ts @@ -1,3 +1,5 @@ +import { Router } from "@/server/router"; + import { getRole, getAllRoles, @@ -9,17 +11,24 @@ import { restoreRole, } from "@/handlers/roles"; -import type { FastifyInstance, FastifyPluginOptions } from "fastify"; +import { + getRolesSchema, + getRoleSchema, + createRoleSchema, + restoreRoleSchema, + updateRoleSchema, + replaceRoleSchema, + removeRoleSchema, + deleteRoleSchema, +} from "@/swagger/role"; -export const setRoleRoutes = (server: FastifyInstance, opts: T, done: () => void): void => { - server.get("/all", getAllRoles); - server.get("/:query", getRole); - server.post("/", createRole); - server.post("/restore", restoreRole); - server.patch("/:query", updateRole); - server.put("/:query", replaceRole); - server.delete("/", removeRole); - server.delete("/delete", deleteRole); - - done(); +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 }); }; diff --git a/src/routes/tariff.routes.ts b/src/routes/tariff.routes.ts index 85ea1fb..35bdfe0 100644 --- a/src/routes/tariff.routes.ts +++ b/src/routes/tariff.routes.ts @@ -1,14 +1,13 @@ +import { Router } from "@/server/router"; + import { createTariff, replaceTariff, getTariff, getTariffs } from "@/handlers/tariff"; import { verifyUser } from "@/handlers/auth/middleware"; -import type { FastifyInstance, FastifyPluginOptions } from "fastify"; -import type { CreateTariffRoute, ReplaceTariffRoute } from "@/types/routes/tariff-routes.type"; +import { getTariffSchema, getTariffsSchema, createTariffsSchema, replaceTariffsSchema } from "@/swagger/tariff"; -export const setTariffRoutes = (server: FastifyInstance, opts: T, done: () => void): void => { - server.get("/:id", getTariff); - server.get("/", getTariffs); - server.post("/", { preHandler: [verifyUser] }, createTariff); - server.put("/", { preHandler: [verifyUser] }, replaceTariff); - - done(); +export const setTariffRoutes = (router: Router): void => { + router.get("/:id", getTariff, { schema: getTariffSchema }); + router.get("/", getTariffs, { schema: getTariffsSchema }); + router.post("/", createTariff, { preHandler: [verifyUser], schema: createTariffsSchema }); + router.put("/", replaceTariff, { preHandler: [verifyUser], schema: replaceTariffsSchema }); }; diff --git a/src/server/index.ts b/src/server/index.ts index ca50842..33511fa 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,6 +1,8 @@ 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"; @@ -43,7 +45,7 @@ export class Server { registerFastifyPlugins(this.fastify, pluginsOptions); } - combineRoutes(this.fastify); + combineRoutes(new Router(this.fastify)); } public start = async () => { @@ -51,7 +53,10 @@ export class Server { const databaseConnection = this.databaseOptions ? mongoConnect(constituteMongoURI(this.databaseOptions)) : null; await Promise.all([databaseConnection, fasticyConnection]) - .then(() => console.info(`server started on ${this.serverOptions?.host}:${this.serverOptions?.port}`)) + .then(() => { + this.fastify.swagger(); + console.info(`server started on ${this.serverOptions?.host}:${this.serverOptions?.port}`); + }) .catch((reason) => console.error(reason)); }; diff --git a/src/server/router.ts b/src/server/router.ts new file mode 100644 index 0000000..fdb238b --- /dev/null +++ b/src/server/router.ts @@ -0,0 +1,77 @@ +import type { + FastifyInstance, + RouteHandlerMethod, + RawRequestDefaultExpression, + RawReplyDefaultExpression, + RouteShorthandOptions, + FastifyPluginOptions, + RawServerDefault, + RouteGenericInterface, +} from "fastify"; + +type HandlerMethod = RouteHandlerMethod< + RawServerDefault, + RawRequestDefaultExpression, + RawReplyDefaultExpression, + 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 = ( + path: string, + handler: HandlerMethod, + options: RouteShorthandOptions + ) => { + this.fastifyInstance.get(path, options, handler); + }; + + public post = ( + path: string, + handler: HandlerMethod, + options: RouteShorthandOptions + ) => { + this.fastifyInstance.post(path, options, handler); + }; + + public put = ( + path: string, + handler: HandlerMethod, + options: RouteShorthandOptions + ) => { + this.fastifyInstance.put(path, options, handler); + }; + + public patch = ( + path: string, + handler: HandlerMethod, + options: RouteShorthandOptions + ) => { + this.fastifyInstance.patch(path, options, handler); + }; + + public delete = ( + path: string, + handler: HandlerMethod, + options: RouteShorthandOptions + ) => { + this.fastifyInstance.delete(path, options, handler); + }; +} diff --git a/src/swagger/account/index.ts b/src/swagger/account/index.ts new file mode 100644 index 0000000..ea8500b --- /dev/null +++ b/src/swagger/account/index.ts @@ -0,0 +1,19 @@ +import { getAccountParams } from "./inputs"; +import { getAccountResponse, createAccountResponse } from "./responses"; + +import type { SwaggerSchema } from "@/types/swagger.type"; + +export const getAccountSchema: SwaggerSchema = { + summary: "Получение информации об аккаунте", + description: "Получение аккаунта по ID", + tags: ["account"], + params: getAccountParams, + response: getAccountResponse, +}; + +export const createAccountSchema: SwaggerSchema = { + summary: "Создание аккаунта", + tags: ["account"], + response: createAccountResponse, + security: [{ bearer: [] }], +}; diff --git a/src/swagger/account/inputs.ts b/src/swagger/account/inputs.ts new file mode 100644 index 0000000..200ee87 --- /dev/null +++ b/src/swagger/account/inputs.ts @@ -0,0 +1,11 @@ +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const getAccountParams: SwaggerMessage = { + type: "object", + properties: { + userId: { + type: "string", + description: "ID пользователя", + }, + }, +}; diff --git a/src/swagger/account/models.ts b/src/swagger/account/models.ts new file mode 100644 index 0000000..5702b8b --- /dev/null +++ b/src/swagger/account/models.ts @@ -0,0 +1,46 @@ +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const account: SwaggerMessage = { + description: "Аккаунт", + type: "object", + properties: { + userId: { type: "string" }, + nickname: { type: "string" }, + avatar: { type: "string" }, + role: { type: "string" }, + isDeleted: { type: "boolean" }, + createdAt: { + type: "string", + format: "date-time", + }, + updatedAt: { + type: "string", + format: "date-time", + }, + deletedAt: { + type: "string", + format: "date-time", + }, + }, + examples: [ + { + userId: "507f1f77bcf86cd799439011", + nickname: "Ivanov Ivan Ivanovich", + avatar: "/media/avatar/default-avatar.jpg", + role: "user", + isDeleted: false, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2017-07-21T17:32:28Z", + }, + { + userId: "507f1f77bcf86cd799439011", + nickname: "Ivanov Ivan Ivanovich", + avatar: "/media/avatar/default-avatar.jpg", + role: "user", + isDeleted: true, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2019-04-14T15:32:15Z", + deletedAt: "2021-08-17T13:23:44Z", + }, + ], +}; diff --git a/src/swagger/account/responses.ts b/src/swagger/account/responses.ts new file mode 100644 index 0000000..8235190 --- /dev/null +++ b/src/swagger/account/responses.ts @@ -0,0 +1,18 @@ +import { swaggerError } from "@/utils/swagger-error"; + +import { account } from "./models"; + +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const getAccountResponse: Record = { + 200: account, + 400: swaggerError(400, "invalid user id"), +}; + +export const createAccountResponse: Record = { + 200: account, + 400: swaggerError(400, "invalid user id"), + 401: swaggerError(401, "invalid token"), + 404: swaggerError(404, "user not found"), + 409: swaggerError(409, "account already exist"), +}; diff --git a/src/swagger/privilege/index.ts b/src/swagger/privilege/index.ts new file mode 100644 index 0000000..77cdc3e --- /dev/null +++ b/src/swagger/privilege/index.ts @@ -0,0 +1,46 @@ +import { privilegeBody, getPrivilegeParams, getServicePrivilegiesParams, getPrivilegiesQuery } from "./inputs"; +import { + getPrivilegeReponse, + getPrivilegiesReponse, + getAllPrivilegiesReponse, + registerPrivilegeResponse, + replacePrivilegeResponse, +} from "./responses"; + +import type { SwaggerSchema } from "@/types/swagger.type"; + +export const getPrivilegiesSchema: SwaggerSchema = { + summary: "Получение всех привелегий", + description: "Получение всех привелегий в разном формате", + tags: ["privilege"], + querystring: getPrivilegiesQuery, + response: getAllPrivilegiesReponse, +}; + +export const getPrivilegeSchema: SwaggerSchema = { + summary: "Получение привилегии по ID", + tags: ["privilege"], + params: getPrivilegeParams, + response: getPrivilegeReponse, +}; + +export const getServicePrivilegiesSchema: SwaggerSchema = { + summary: "Получение привилегий сервиса", + tags: ["privilege"], + params: getServicePrivilegiesParams, + response: getPrivilegiesReponse, +}; + +export const registerPrivilegeSchema: SwaggerSchema = { + summary: "Регистрация привелегии сервиса", + tags: ["privilege"], + body: privilegeBody, + response: registerPrivilegeResponse, +}; + +export const replacePrivilegeSchema: SwaggerSchema = { + summary: "Замена привилегии сервиса", + tags: ["privilege"], + body: privilegeBody, + response: replacePrivilegeResponse, +}; diff --git a/src/swagger/privilege/inputs.ts b/src/swagger/privilege/inputs.ts new file mode 100644 index 0000000..3212f0e --- /dev/null +++ b/src/swagger/privilege/inputs.ts @@ -0,0 +1,62 @@ +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const privilegeBody: SwaggerMessage = { + type: "object", + required: ["name", "privilegeId", "serviceKey", "description", "type", "value", "price"], + properties: { + name: { type: "string" }, + privilegeId: { type: "string" }, + serviceKey: { type: "string" }, + description: { type: "string" }, + type: { type: "string" }, + value: { type: "string" }, + price: { type: "number" }, + }, + examples: [ + { + name: "507f1f77bcf86cd799439011", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + }, + ], +}; + +export const getPrivilegeParams: SwaggerMessage = { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + description: "ID привилегии", + }, + }, + examples: [{ id: "507f1f77bcf86cd799439011" }], +}; + +export const getServicePrivilegiesParams: SwaggerMessage = { + type: "object", + required: ["serviceKey"], + properties: { + serviceKey: { + type: "string", + description: "ID привилегии", + }, + }, + examples: [{ id: "507f1f77bcf86cd799439011" }], +}; + +export const getPrivilegiesQuery: SwaggerMessage = { + type: "object", + properties: { + format: { + type: "string", + description: + "Есть два формата списка привелегий, в виде массива и в виде объекта ключ-значение, где ключём является ключ сервиса", + }, + }, + examples: [{ format: "array" }, { format: "map" }], +}; diff --git a/src/swagger/privilege/models.ts b/src/swagger/privilege/models.ts new file mode 100644 index 0000000..452c247 --- /dev/null +++ b/src/swagger/privilege/models.ts @@ -0,0 +1,103 @@ +import type { SwaggerMessage, SwaggerValueType } from "@/types/swagger.type"; + +const privilegeExamples: SwaggerValueType[] = [ + { + name: "use count", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + isDeleted: false, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2017-07-21T17:32:28Z", + }, + { + name: "use count", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + isDeleted: false, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2017-07-21T17:32:28Z", + }, +]; + +export const privilege: SwaggerMessage = { + type: "object", + description: "Привилегия", + properties: { + name: { type: "string" }, + privilegeId: { type: "string" }, + serviceKey: { type: "string" }, + description: { type: "string" }, + type: { type: "string" }, + value: { type: "string" }, + price: { type: "number" }, + isDeleted: { type: "boolean" }, + createdAt: { + type: "string", + format: "date-time", + }, + updatedAt: { + type: "string", + format: "date-time", + }, + deletedAt: { + type: "string", + format: "date-time", + }, + }, + examples: privilegeExamples, +}; + +export const privilegiesMessage: SwaggerMessage = { + type: "object", + description: "Привилегии", + oneOf: [ + { + type: "array", + items: privilege, + }, + { + type: "object", + additionalProperties: privilege, + }, + ], + examples: [ + privilegeExamples, + { + "docx-templater-service": [ + { + name: "use count", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + isDeleted: false, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2017-07-21T17:32:28Z", + }, + { + name: "use count", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + isDeleted: true, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2019-04-14T15:32:15Z", + deletedAt: "2021-08-17T13:23:44Z", + }, + ], + }, + ], +}; diff --git a/src/swagger/privilege/responses.ts b/src/swagger/privilege/responses.ts new file mode 100644 index 0000000..2e9195d --- /dev/null +++ b/src/swagger/privilege/responses.ts @@ -0,0 +1,35 @@ +import { swaggerError } from "@/utils/swagger-error"; + +import { privilege, privilegiesMessage } from "./models"; + +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const getAllPrivilegiesReponse: Record = { + 200: privilegiesMessage, + 500: swaggerError(500, "format not defined"), +}; + +export const getPrivilegiesReponse: Record = { + 200: { + type: "array", + items: privilege, + }, +}; + +export const getPrivilegeReponse: Record = { + 200: privilege, + 400: swaggerError(400, "invalid id"), + 404: swaggerError(404, "privilege not found"), +}; + +export const registerPrivilegeResponse: Record = { + 200: privilege, + 400: swaggerError(400, "price must be a number"), + 409: swaggerError(409, "privilege already exist"), +}; + +export const replacePrivilegeResponse: Record = { + 200: privilege, + 400: swaggerError(400, "invalid 'type' value"), + 404: swaggerError(404, "privilege not found"), +}; diff --git a/src/swagger/role/index.ts b/src/swagger/role/index.ts new file mode 100644 index 0000000..46fe1f6 --- /dev/null +++ b/src/swagger/role/index.ts @@ -0,0 +1,71 @@ +import { getRoleParams, roleBody, getRoleByIdBody, updateRoleBody } from "./inputs"; +import { + getRolesReponse, + getRoleReponse, + createRoleReponse, + restoreRoleReponse, + updateRoleReponse, + removeRoleReponse, +} from "./responses"; + +import type { SwaggerSchema } from "@/types/swagger.type"; + +export const getRolesSchema: SwaggerSchema = { + summary: "Получение всех ролей", + tags: ["role"], + response: getRolesReponse, +}; + +export const getRoleSchema: SwaggerSchema = { + summary: "Получение роли", + tags: ["role"], + params: getRoleParams, + response: getRoleReponse, +}; + +export const createRoleSchema: SwaggerSchema = { + summary: "Создание роли", + tags: ["role"], + body: roleBody, + response: createRoleReponse, +}; + +export const restoreRoleSchema: SwaggerSchema = { + summary: "Восстановление удалённой роли", + description: "Восстанавливает удалённую роль, которая ещё существует в БД", + tags: ["role"], + body: getRoleByIdBody, + response: restoreRoleReponse, +}; + +export const updateRoleSchema: SwaggerSchema = { + summary: "Обновление роли", + tags: ["role"], + params: getRoleParams, + body: updateRoleBody, + response: updateRoleReponse, +}; + +export const replaceRoleSchema: SwaggerSchema = { + summary: "Замена роли", + tags: ["role"], + params: getRoleParams, + body: roleBody, + response: updateRoleReponse, +}; + +export const removeRoleSchema: SwaggerSchema = { + summary: "Удаление роли", + description: "Помечает роль удалённой, но не удаляет её окончательно", + tags: ["role"], + body: getRoleByIdBody, + response: removeRoleReponse, +}; + +export const deleteRoleSchema: SwaggerSchema = { + summary: "Получение всех ролей", + description: "Удаляет роль окончательно", + tags: ["role"], + body: getRoleByIdBody, + response: getRolesReponse, +}; diff --git a/src/swagger/role/inputs.ts b/src/swagger/role/inputs.ts new file mode 100644 index 0000000..657fa91 --- /dev/null +++ b/src/swagger/role/inputs.ts @@ -0,0 +1,58 @@ +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const getRoleParams: SwaggerMessage = { + type: "object", + required: ["query"], + properties: { + query: { + type: "string", + description: "query может принимать как id, так и name роли", + }, + }, + examples: [{ query: "507f1f77bcf86cd799439011" }, { query: "admin" }], +}; + +export const roleBody: SwaggerMessage = { + type: "object", + required: ["name", "permissions"], + properties: { + name: { + type: "string", + description: "Название роли", + }, + permissions: { + type: "array", + description: "Массив разрешений", + items: { type: "string" }, + }, + }, + examples: [{ name: "admin", permissions: ["read", "write"] }], +}; + +export const updateRoleBody: SwaggerMessage = { + type: "object", + properties: { + name: { + type: "string", + description: "Название роли", + }, + permissions: { + type: "array", + description: "Массив разрешений", + items: { type: "string" }, + }, + }, + examples: [{ name: "admin", permissions: ["read", "write"] }], +}; + +export const getRoleByIdBody: SwaggerMessage = { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + description: "ID роли", + }, + }, + examples: [{ id: "507f1f77bcf86cd799439011" }], +}; diff --git a/src/swagger/role/models.ts b/src/swagger/role/models.ts new file mode 100644 index 0000000..8a79fa7 --- /dev/null +++ b/src/swagger/role/models.ts @@ -0,0 +1,29 @@ +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const role: SwaggerMessage = { + type: "object", + description: "Роль", + properties: { + name: { type: "string" }, + permissions: { + type: "object", + additionalProperties: { type: "boolean" }, + }, + }, + examples: [ + { + name: "user", + permissions: { + read: true, + write: false, + }, + }, + { + name: "admin", + permissions: { + read: true, + write: true, + }, + }, + ], +}; diff --git a/src/swagger/role/responses.ts b/src/swagger/role/responses.ts new file mode 100644 index 0000000..2cc43ac --- /dev/null +++ b/src/swagger/role/responses.ts @@ -0,0 +1,50 @@ +import { swaggerError } from "@/utils/swagger-error"; + +import { role } from "./models"; + +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const getRolesReponse: Record = { + 200: { + type: "array", + items: role, + }, +}; + +export const getRoleReponse: Record = { + 200: role, + 400: swaggerError(400, "query is empty"), + 404: swaggerError(404, "role not found"), +}; + +export const createRoleReponse: Record = { + 200: role, + 400: swaggerError(400, "field is empty"), + 409: swaggerError(409, "role already exist"), +}; + +export const restoreRoleReponse: Record = { + 200: role, + 400: swaggerError(400, "wrong id"), + 404: swaggerError(404, "role by id not found"), + 409: swaggerError(409, "role not removed"), +}; + +export const updateRoleReponse: Record = { + 200: role, + 400: swaggerError(400, "either name or permissions must be filled"), + 404: swaggerError(404, "role not found"), +}; + +export const removeRoleReponse: Record = { + 200: role, + 400: swaggerError(400, "wrong id"), + 404: swaggerError(404, "role by id not found"), + 409: swaggerError(409, "role already deleted"), +}; + +export const deleteRoleReponse: Record = { + 200: role, + 400: swaggerError(400, "wrong id"), + 404: swaggerError(404, "role not found"), +}; diff --git a/src/swagger/tariff/index.ts b/src/swagger/tariff/index.ts new file mode 100644 index 0000000..087a9a9 --- /dev/null +++ b/src/swagger/tariff/index.ts @@ -0,0 +1,30 @@ +import { getTariffParams, tariffBody } from "./inputs"; +import { getTariffReponse, getTariffsReponse, createTariffReponse, replaceTariffReponse } from "./responses"; + +import type { SwaggerSchema } from "@/types/swagger.type"; + +export const getTariffSchema: SwaggerSchema = { + summary: "Получение тарифа", + tags: ["tariff"], + params: getTariffParams, + response: getTariffReponse, +}; + +export const getTariffsSchema: SwaggerSchema = { + summary: "Получение списка тарифов", + tags: ["tariff"], + response: getTariffsReponse, +}; + +export const createTariffsSchema: SwaggerSchema = { + summary: "Создание тарифа", + tags: ["tariff"], + body: tariffBody, + response: createTariffReponse, +}; +export const replaceTariffsSchema: SwaggerSchema = { + summary: "Замена тарифа", + tags: ["tariff"], + body: tariffBody, + response: replaceTariffReponse, +}; diff --git a/src/swagger/tariff/inputs.ts b/src/swagger/tariff/inputs.ts new file mode 100644 index 0000000..c2d66fa --- /dev/null +++ b/src/swagger/tariff/inputs.ts @@ -0,0 +1,48 @@ +import { privilege } from "@/swagger/privilege/models"; + +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const getTariffParams: SwaggerMessage = { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + description: "ID тарифа", + }, + }, + examples: [{ id: "507f1f77bcf86cd799439011" }], +}; + +export const tariffBody: SwaggerMessage = { + type: "object", + description: "Тариф", + required: ["name", "price", "isCustom", "privilegies"], + properties: { + name: { type: "string" }, + price: { type: "number" }, + isCustom: { type: "boolean" }, + privilegies: { + type: "array", + items: privilege, + }, + }, + examples: [ + { + name: "user", + price: 14000, + isCustom: false, + privilegies: [ + { + name: "507f1f77bcf86cd799439011", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + }, + ], + }, + ], +}; diff --git a/src/swagger/tariff/models.ts b/src/swagger/tariff/models.ts new file mode 100644 index 0000000..93e4d95 --- /dev/null +++ b/src/swagger/tariff/models.ts @@ -0,0 +1,94 @@ +import type { SwaggerMessage } from "@/types/swagger.type"; + +const privilege: SwaggerMessage = { + type: "object", + description: "Привилегия", + properties: { + name: { type: "string" }, + privilegeId: { type: "string" }, + serviceKey: { type: "string" }, + description: { type: "string" }, + type: { type: "string" }, + value: { type: "string" }, + price: { type: "number" }, + }, + examples: [ + { + name: "507f1f77bcf86cd799439011", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + }, + ], +}; + +export const tariff: SwaggerMessage = { + type: "object", + description: "Тариф", + properties: { + name: { type: "string" }, + price: { type: "number" }, + isCustom: { type: "boolean" }, + privilegies: { + type: "object", + additionalProperties: privilege, + }, + isDeleted: { type: "boolean" }, + createdAt: { + type: "string", + format: "date-time", + }, + updatedAt: { + type: "string", + format: "date-time", + }, + deletedAt: { + type: "string", + format: "date-time", + }, + }, + examples: [ + { + name: "user", + price: 14000, + isCustom: false, + privilegies: { + "507f1f77bcf86cd799439011": { + name: "507f1f77bcf86cd799439011", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + }, + }, + isDeleted: false, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2017-07-21T17:32:28Z", + }, + { + name: "user", + price: 14000, + isCustom: false, + privilegies: { + "507f1f77bcf86cd799439011": { + name: "507f1f77bcf86cd799439011", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + }, + }, + isDeleted: true, + createdAt: "2017-07-21T17:32:28Z", + updatedAt: "2019-04-14T15:32:15Z", + deletedAt: "2021-08-17T13:23:44Z", + }, + ], +}; diff --git a/src/swagger/tariff/responses.ts b/src/swagger/tariff/responses.ts new file mode 100644 index 0000000..d4d7b23 --- /dev/null +++ b/src/swagger/tariff/responses.ts @@ -0,0 +1,30 @@ +import { swaggerError } from "@/utils/swagger-error"; + +import { tariff } from "./models"; + +import type { SwaggerMessage } from "@/types/swagger.type"; + +export const getTariffReponse: Record = { + 200: tariff, + 400: swaggerError(400, "invalid id"), + 404: swaggerError(404, "tariff not found"), +}; + +export const getTariffsReponse: Record = { + 200: { + type: "array", + items: tariff, + }, +}; + +export const createTariffReponse: Record = { + 200: tariff, + 400: swaggerError(400, "invalid 'price' value"), + 404: swaggerError(404, "privilege with id not found"), +}; + +export const replaceTariffReponse: Record = { + 200: tariff, + 400: swaggerError(400, "invalid id"), + 404: swaggerError(404, "tariff not found"), +}; diff --git a/src/types/messages/tariff-message.type.ts b/src/types/messages/tariff-message.type.ts index e420035..ccce19e 100644 --- a/src/types/messages/tariff-message.type.ts +++ b/src/types/messages/tariff-message.type.ts @@ -1,10 +1,10 @@ -import type { EloquentModel } from "../models/eloquent-model.type"; +import type { Eloquent } from "../models/eloquent.type"; import type { Privilege } from "../models/privilege.type"; import type { Tariff } from "../models/tariff.type"; import type { ObjectWithPossibleFields } from "../object-with-possible-fields"; export type TariffMessage = ObjectWithPossibleFields< - Omit & { + Omit & { privilegies: Privilege[]; } >; diff --git a/src/types/models/account.type.ts b/src/types/models/account.type.ts index 06e5beb..ab1144d 100644 --- a/src/types/models/account.type.ts +++ b/src/types/models/account.type.ts @@ -1,7 +1,8 @@ -export type Account = { +import type { Eloquent } from "./eloquent.type"; + +export type Account = Eloquent & { userId: string; nickname: string; avatar: string; role: string; - privilegies: Record; }; diff --git a/src/types/models/eloquent-model.type.ts b/src/types/models/eloquent.type.ts similarity index 73% rename from src/types/models/eloquent-model.type.ts rename to src/types/models/eloquent.type.ts index d034691..7f4e259 100644 --- a/src/types/models/eloquent-model.type.ts +++ b/src/types/models/eloquent.type.ts @@ -1,4 +1,4 @@ -export type EloquentModel = { +export type Eloquent = { createdAt: Date; updatedAt: Date; deletedAt: Date; diff --git a/src/types/models/privilege.type.ts b/src/types/models/privilege.type.ts index d941ff5..0070cf1 100644 --- a/src/types/models/privilege.type.ts +++ b/src/types/models/privilege.type.ts @@ -1,6 +1,6 @@ -import type { EloquentModel } from "./eloquent-model.type"; +import type { Eloquent } from "./eloquent.type"; -export type Privilege = EloquentModel & { +export type Privilege = Eloquent & { name: string; privilegeId: string; serviceKey: string; diff --git a/src/types/models/role.type.ts b/src/types/models/role.type.ts index 820b8e8..b00954b 100644 --- a/src/types/models/role.type.ts +++ b/src/types/models/role.type.ts @@ -1,6 +1,6 @@ -import type { EloquentModel } from "./eloquent-model.type"; +import type { Eloquent } from "./eloquent.type"; -export type Role = EloquentModel & { +export type Role = Eloquent & { name: string; permissions: Record; }; diff --git a/src/types/models/tariff.type.ts b/src/types/models/tariff.type.ts index 67120e0..2b72d87 100644 --- a/src/types/models/tariff.type.ts +++ b/src/types/models/tariff.type.ts @@ -1,9 +1,9 @@ import type { Privilege } from "./privilege.type"; -import type { EloquentModel } from "./eloquent-model.type"; +import type { Eloquent } from "./eloquent.type"; -export type Tariff = EloquentModel & { +export type Tariff = Eloquent & { name: string; price: number; isCustom: boolean; - privilegies: Record; + privilegies: Record>; }; diff --git a/src/types/models/user.type.ts b/src/types/models/user.type.ts index 8a0457e..0215410 100644 --- a/src/types/models/user.type.ts +++ b/src/types/models/user.type.ts @@ -1,6 +1,6 @@ -import type { EloquentModel } from "./eloquent-model.type"; +import type { Eloquent } from "./eloquent.type"; -export type User = EloquentModel & { +export type User = Eloquent & { _id: string; login: string; email: string; diff --git a/src/types/routes/account-routes.type.ts b/src/types/routes/account-routes.type.ts deleted file mode 100644 index fe1fc9f..0000000 --- a/src/types/routes/account-routes.type.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { RequestGenericInterface } from "fastify"; - -export type GetAccountRoute = RequestGenericInterface & { - Params?: { - userId?: string; - }; -}; - -export type CreateAccountRoute = RequestGenericInterface & { - Params?: { - userId?: string; - }; -}; diff --git a/src/types/routes/tariff-routes.type.ts b/src/types/routes/tariff-routes.type.ts deleted file mode 100644 index 042e14b..0000000 --- a/src/types/routes/tariff-routes.type.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { RequestGenericInterface } from "fastify"; -import type { TariffMessage } from "../messages/tariff-message.type"; - -export type CreateTariffRoute = RequestGenericInterface & { - Body?: TariffMessage; -}; - -export type ReplaceTariffRoute = RequestGenericInterface & { - Body?: TariffMessage; - Params?: { id?: string }; -}; diff --git a/src/types/swagger.type.ts b/src/types/swagger.type.ts new file mode 100644 index 0000000..d355731 --- /dev/null +++ b/src/types/swagger.type.ts @@ -0,0 +1,48 @@ +import type { FastifySchema } from "fastify"; + +export type SwaggerValueType = + | string + | number + | boolean + | null + | SwaggerValueType[] + | { [key: string]: SwaggerValueType }; + +type SwaggerMediaType = "object" | "string" | "integer" | "boolean" | "array" | "number"; +type SwaggerMediaFormat = + | "binary" + | "base64" + | "uuid" + | "email" + | "date" + | "date-time" + | "password" + | "byte" + | "uri" + | "hostname" + | "ipv4" + | "ipv6"; + +export type SwaggerMessage = { + type: SwaggerMediaType; + description?: string; + format?: SwaggerMediaFormat; + pattern?: string; + nullable?: boolean; + uniqueItems?: boolean; + writeOnly?: boolean; + readOnly?: boolean; + items?: SwaggerMessage; + additionalProperties?: SwaggerMessage; + properties?: Record>; + examples?: SwaggerValueType[]; + required?: string[]; + oneOf?: SwaggerMessage[]; + allOf?: SwaggerMessage[]; +}; + +export type SwaggerSchema = FastifySchema & { + params?: SwaggerMessage; + body?: SwaggerMessage; + response?: Record; +}; diff --git a/src/utils/swagger-error.ts b/src/utils/swagger-error.ts new file mode 100644 index 0000000..9587fa4 --- /dev/null +++ b/src/utils/swagger-error.ts @@ -0,0 +1,19 @@ +import type { SwaggerMessage } from "@/types/swagger.type"; + +const STATUS_CODE_MAP: Record = { + 400: "Bad Request", + 401: "Unauthorized", + 404: "Not Found", + 409: "Conflict", + 500: "Internal Server Error", +}; + +export const swaggerError = (code: number, message: string): SwaggerMessage => ({ + type: "object", + properties: { + statusCode: { type: "integer" }, + error: { type: "string" }, + message: { type: "string" }, + }, + examples: [{ statusCode: code, error: STATUS_CODE_MAP[code], message: message }], +}); diff --git a/yarn.lock b/yarn.lock index 04e14ef..6c32101 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1204,6 +1204,11 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@fastify/accept-negotiator@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz#c1c66b3b771c09742a54dd5bc87c582f6b0630ff" + integrity sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ== + "@fastify/ajv-compiler@^3.3.1": version "3.4.0" resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz#e001b7e234b5b704654b1d617d69fa63c348f2a7" @@ -1257,6 +1262,41 @@ fastify-plugin "^4.0.0" steed "^1.1.3" +"@fastify/static@^6.0.0": + version "6.6.0" + resolved "https://registry.yarnpkg.com/@fastify/static/-/static-6.6.0.tgz#763244583abf7a4734bff7b1d1aef0ef445393fb" + integrity sha512-UiYSN2dUmDZ48M40xdIwY1dPwSSYD7c+wtoIQP8y7wyxCwcUtf1YT5/Q4n1uJsBF1fySvuo9njQZKlHeiKy4HQ== + dependencies: + "@fastify/accept-negotiator" "^1.0.0" + content-disposition "^0.5.3" + fastify-plugin "^4.0.0" + glob "^8.0.1" + p-limit "^3.1.0" + readable-stream "^4.0.0" + send "^0.18.0" + +"@fastify/swagger-ui@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@fastify/swagger-ui/-/swagger-ui-1.3.0.tgz#967afd8bfa87a539b83ae6bc986f3589e9fdb98a" + integrity sha512-Q6vvIyTd1gj0h0IoDAAUX3SBBiL1pybXP0FmFbD4yLMcACIZ7xm8oHCf5lMc3rNC69KhbywuSst3iHgp23x8SA== + dependencies: + "@fastify/static" "^6.0.0" + fastify-plugin "^4.0.0" + openapi-types "^12.0.2" + rfdc "^1.3.0" + yaml "^2.1.3" + +"@fastify/swagger@^8.2.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@fastify/swagger/-/swagger-8.2.1.tgz#a5f744868c25c49bde2469f5412ce0405aac150a" + integrity sha512-nYP/3ncrI5YmaGiJf6m+CLdFrdlWSsASHBPqP9uN9/oFFwDJwdUtq0ylmvObxzqWNVt9zT50iT/uvIndVEsvbg== + dependencies: + fastify-plugin "^4.0.0" + json-schema-resolver "^2.0.0" + openapi-types "^12.0.0" + rfdc "^1.3.0" + yaml "^2.1.1" + "@humanwhocodes/config-array@^0.11.6": version "0.11.7" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" @@ -2184,6 +2224,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2410,6 +2457,13 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +content-disposition@^0.5.3: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + conventional-changelog-angular@^5.0.11: version "5.0.13" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" @@ -2489,6 +2543,13 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== +debug@2.6.9, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@4, debug@4.x, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -2496,13 +2557,6 @@ debug@4, debug@4.x, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debu dependencies: ms "2.1.2" -debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -2561,6 +2615,16 @@ denque@^2.1.0: resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2616,6 +2680,11 @@ ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: dependencies: safe-buffer "^5.0.1" +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + electron-to-chromium@^1.4.251: version "1.4.284" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" @@ -2631,6 +2700,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2704,6 +2778,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2899,6 +2978,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -3179,6 +3263,11 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -3307,6 +3396,17 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.1: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + global-dirs@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -3432,6 +3532,17 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -3499,7 +3610,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4131,6 +4242,15 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-resolver@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz#d17fdf53560e6bc9af084b930fee27f6ce4a03b6" + integrity sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg== + dependencies: + debug "^4.1.1" + rfdc "^1.1.4" + uri-js "^4.2.2" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -4409,6 +4529,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -4431,6 +4556,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.2.tgz#0939d7d6f0898acbd1508abe534d1929368a8fff" + integrity sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -4669,6 +4801,13 @@ on-exit-leak-free@^2.1.0: resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4692,6 +4831,11 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" +openapi-types@^12.0.0, openapi-types@^12.0.2: + version "12.1.0" + resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.0.tgz#bd01acc937b73c9f6db2ac2031bf0231e21ebff0" + integrity sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA== + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -4935,6 +5079,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + react-is@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" @@ -5070,7 +5219,7 @@ reusify@^1.0.0, reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rfdc@^1.2.0, rfdc@^1.3.0: +rfdc@^1.1.4, rfdc@^1.2.0, rfdc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== @@ -5089,7 +5238,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@^5.0.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5161,11 +5310,35 @@ semver@~7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +send@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + set-cookie-parser@^2.4.1: version "2.5.1" resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -5318,6 +5491,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + steed@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/steed/-/steed-1.1.3.tgz#f1525dd5adb12eb21bf74749537668d625b9abc5" @@ -5546,6 +5724,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -5847,6 +6030,11 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.1.1, yaml@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.3.tgz#9b3a4c8aff9821b696275c79a8bee8399d945207" + integrity sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg== + yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" From 6d0e630e89c224e92c9f09832cff1bc4f3d3e931 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sat, 24 Dec 2022 12:18:28 +0000 Subject: [PATCH 06/10] feat: privilege handlers --- src/handlers/privilege/helpers.ts | 2 +- src/handlers/privilege/index.ts | 121 +++++++++++++++++++++++++++++ src/handlers/privilege/types.ts | 6 ++ src/routes/privilege.routes.ts | 9 +++ src/server/router.ts | 8 +- src/swagger/privilege/index.ts | 32 +++++++- src/swagger/privilege/inputs.ts | 5 ++ src/swagger/privilege/responses.ts | 22 ++++++ 8 files changed, 199 insertions(+), 6 deletions(-) diff --git a/src/handlers/privilege/helpers.ts b/src/handlers/privilege/helpers.ts index b5d95d3..2a8fa1f 100644 --- a/src/handlers/privilege/helpers.ts +++ b/src/handlers/privilege/helpers.ts @@ -5,7 +5,7 @@ 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"); + return new Error("invalid value"); } if (isNaN(Number(privilege.price))) { diff --git a/src/handlers/privilege/index.ts b/src/handlers/privilege/index.ts index 2193a59..b9e816b 100644 --- a/src/handlers/privilege/index.ts +++ b/src/handlers/privilege/index.ts @@ -11,8 +11,56 @@ import type { GetServicePrivilegiesRequest, RegisterPrivilegeRequest, GetPrivilegeRequest, + RegisterPrivilegiesRequest, } from "./types"; +export const registerPrivilegies = async (request: RegisterPrivilegiesRequest, reply: FastifyReply) => { + const [requestBody, errorEmpty] = validateEmptyFields(request.body || {}, ["privilegies"]); + + if (errorEmpty) { + reply.status(400); + return errorEmpty; + } + + if (!requestBody.privilegies.length) { + reply.status(400); + return new Error("empty privilege array"); + } + + for (const rawPrivilege of requestBody.privilegies) { + const errorInvalid = validatePrivilege(rawPrivilege); + + if (errorInvalid) { + 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 || {}, @@ -141,7 +189,80 @@ export const replacePrivilege = async (request: RegisterPrivilegeRequest, reply: type: requestBody.type, value: requestBody.value, price: requestBody.price, + updatedAt: new Date(), }); return privilege; }; + +export const replacePrivilegies = async (request: RegisterPrivilegiesRequest, reply: FastifyReply) => { + const [requestBody, errorEmpty] = validateEmptyFields(request.body || {}, ["privilegies"]); + + if (errorEmpty) { + reply.status(400); + return errorEmpty; + } + + if (!requestBody.privilegies.length) { + reply.status(400); + return new Error("empty privilege array"); + } + + for (const rawPrivilege of requestBody.privilegies) { + const errorInvalid = validatePrivilege(rawPrivilege); + + if (errorInvalid) { + reply.status(400); + return errorInvalid; + } + } + + const replacePrivilegeRequests = requestBody.privilegies.map(async (privilege) => { + await PrivilegeModel.replaceOne( + { 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, + updatedAt: new Date(), + isDeleted: false, + }, + } + ).lean(); + + return privilege; + }); + + return Promise.all(replacePrivilegeRequests); +}; + +export const removePrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => { + const [requestParams, error] = validateEmptyFields(request.params || {}, ["id"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(requestParams.id)) { + reply.status(400); + return new Error("invalid id"); + } + + const privilege = await PrivilegeModel.findOneAndUpdate( + { privilegeId: requestParams.id }, + { $set: { isDeleted: true, deletedAt: new Date() } } + ); + + if (!privilege) { + reply.status(404); + return new Error("privilege not found"); + } + + return privilege; +}; diff --git a/src/handlers/privilege/types.ts b/src/handlers/privilege/types.ts index 76e78c2..97edaf0 100644 --- a/src/handlers/privilege/types.ts +++ b/src/handlers/privilege/types.ts @@ -22,6 +22,12 @@ export type RegisterPrivilegeRequest = FastifyRequest<{ Body?: RawPrivilege; }>; +export type RegisterPrivilegiesRequest = FastifyRequest<{ + Body?: { + privilegies?: RawPrivilege[]; + }; +}>; + export type GetServicePrivilegiesRequest = FastifyRequest<{ Params?: { serviceKey?: string; diff --git a/src/routes/privilege.routes.ts b/src/routes/privilege.routes.ts index 2726088..e9fb140 100644 --- a/src/routes/privilege.routes.ts +++ b/src/routes/privilege.routes.ts @@ -6,6 +6,9 @@ import { getPrivilege, getServicePrivilegies, replacePrivilege, + registerPrivilegies, + removePrivilege, + replacePrivilegies, } from "@/handlers/privilege"; import { @@ -13,7 +16,10 @@ import { getPrivilegeSchema, getServicePrivilegiesSchema, registerPrivilegeSchema, + registerPrivilegiesSchema, replacePrivilegeSchema, + replacePrivilegiesSchema, + removePrivilegeSchema, } from "@/swagger/privilege"; export const setPrivilegeRoutes = (router: Router): void => { @@ -21,5 +27,8 @@ export const setPrivilegeRoutes = (router: Router): void => { router.get("/:id", getPrivilege, { schema: getPrivilegeSchema }); router.get("/service/:serviceKey", getServicePrivilegies, { schema: getServicePrivilegiesSchema }); router.post("/", registerPrivilege, { schema: registerPrivilegeSchema }); + router.post("/many", registerPrivilegies, { schema: registerPrivilegiesSchema }); router.put("/", replacePrivilege, { schema: replacePrivilegeSchema }); + router.put("/many", replacePrivilegies, { schema: replacePrivilegiesSchema }); + router.delete("/", removePrivilege, { schema: removePrivilegeSchema }); }; diff --git a/src/server/router.ts b/src/server/router.ts index fdb238b..6ddb5a0 100644 --- a/src/server/router.ts +++ b/src/server/router.ts @@ -46,7 +46,7 @@ export class Router { public post = ( path: string, handler: HandlerMethod, - options: RouteShorthandOptions + options: RouteShorthandOptions = {} ) => { this.fastifyInstance.post(path, options, handler); }; @@ -54,7 +54,7 @@ export class Router { public put = ( path: string, handler: HandlerMethod, - options: RouteShorthandOptions + options: RouteShorthandOptions = {} ) => { this.fastifyInstance.put(path, options, handler); }; @@ -62,7 +62,7 @@ export class Router { public patch = ( path: string, handler: HandlerMethod, - options: RouteShorthandOptions + options: RouteShorthandOptions = {} ) => { this.fastifyInstance.patch(path, options, handler); }; @@ -70,7 +70,7 @@ export class Router { public delete = ( path: string, handler: HandlerMethod, - options: RouteShorthandOptions + options: RouteShorthandOptions = {} ) => { this.fastifyInstance.delete(path, options, handler); }; diff --git a/src/swagger/privilege/index.ts b/src/swagger/privilege/index.ts index 77cdc3e..bc84698 100644 --- a/src/swagger/privilege/index.ts +++ b/src/swagger/privilege/index.ts @@ -1,10 +1,19 @@ -import { privilegeBody, getPrivilegeParams, getServicePrivilegiesParams, getPrivilegiesQuery } from "./inputs"; +import { + privilegeBody, + privilegiesBody, + getPrivilegeParams, + getServicePrivilegiesParams, + getPrivilegiesQuery, +} from "./inputs"; import { getPrivilegeReponse, getPrivilegiesReponse, getAllPrivilegiesReponse, registerPrivilegeResponse, replacePrivilegeResponse, + registerPrivilegiesResponse, + replacePrivilegiesResponse, + removePrivilegeResponse, } from "./responses"; import type { SwaggerSchema } from "@/types/swagger.type"; @@ -38,9 +47,30 @@ export const registerPrivilegeSchema: SwaggerSchema = { response: registerPrivilegeResponse, }; +export const registerPrivilegiesSchema: SwaggerSchema = { + summary: "Регистрация привелегий сервиса", + tags: ["privilege"], + body: privilegiesBody, + response: registerPrivilegiesResponse, +}; + export const replacePrivilegeSchema: SwaggerSchema = { summary: "Замена привилегии сервиса", tags: ["privilege"], body: privilegeBody, response: replacePrivilegeResponse, }; + +export const replacePrivilegiesSchema: SwaggerSchema = { + summary: "Замена привилегий сервиса", + tags: ["privilege"], + body: privilegiesBody, + response: replacePrivilegiesResponse, +}; + +export const removePrivilegeSchema: SwaggerSchema = { + summary: "Удаление привелегии", + tags: ["privilege"], + body: getPrivilegeParams, + response: removePrivilegeResponse, +}; diff --git a/src/swagger/privilege/inputs.ts b/src/swagger/privilege/inputs.ts index 3212f0e..7268306 100644 --- a/src/swagger/privilege/inputs.ts +++ b/src/swagger/privilege/inputs.ts @@ -25,6 +25,11 @@ export const privilegeBody: SwaggerMessage = { ], }; +export const privilegiesBody: SwaggerMessage = { + type: "array", + items: privilegeBody, +}; + export const getPrivilegeParams: SwaggerMessage = { type: "object", required: ["id"], diff --git a/src/swagger/privilege/responses.ts b/src/swagger/privilege/responses.ts index 2e9195d..880baa3 100644 --- a/src/swagger/privilege/responses.ts +++ b/src/swagger/privilege/responses.ts @@ -28,8 +28,30 @@ export const registerPrivilegeResponse: Record = { 409: swaggerError(409, "privilege already exist"), }; +export const registerPrivilegiesResponse: Record = { + 200: { + type: "array", + items: privilege, + }, + 400: swaggerError(400, "price must be a number"), +}; + export const replacePrivilegeResponse: Record = { 200: privilege, 400: swaggerError(400, "invalid 'type' value"), 404: swaggerError(404, "privilege not found"), }; + +export const replacePrivilegiesResponse: Record = { + 200: { + type: "array", + items: privilege, + }, + 400: swaggerError(400, "invalid 'type' value"), +}; + +export const removePrivilegeResponse: Record = { + 200: privilege, + 400: swaggerError(400, "invalid id"), + 404: swaggerError(404, "privilege not found"), +}; From 5fe44bb6a225a15ffd81c16582124a22d813ec55 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sun, 25 Dec 2022 20:10:23 +0000 Subject: [PATCH 07/10] fix: swagger docs & handlers --- .env.example | 2 +- README.md | 2 + docs/server/README.md | 14 +++++ docs/swagger/README.md | 38 +++++++++++ src/configuration/register-fastify-plugins.ts | 32 ++-------- src/constants/default.ts | 44 +++++++++++-- src/handlers/account/index.ts | 43 +++++++++++-- src/handlers/account/types.ts | 7 +++ src/handlers/privilege/helpers.ts | 17 +---- src/handlers/privilege/index.ts | 56 +++++++---------- src/handlers/privilege/types.ts | 10 +-- src/handlers/tariff/helpers.ts | 6 +- src/handlers/tariff/index.ts | 42 ++++++++----- src/routes/account.routes.ts | 6 +- src/routes/privilege.routes.ts | 5 +- src/routes/tariff.routes.ts | 2 +- src/server/index.ts | 9 +-- src/server/router.ts | 2 +- src/swagger/account/index.ts | 19 +++++- src/swagger/account/inputs.ts | 16 +++++ src/swagger/account/models.ts | 3 + src/swagger/account/responses.ts | 15 +++++ src/swagger/privilege/index.ts | 23 +++---- src/swagger/privilege/inputs.ts | 33 +++++----- src/swagger/privilege/models.ts | 63 +------------------ src/swagger/privilege/responses.ts | 44 ++++++++++++- src/swagger/role/models.ts | 3 + src/swagger/role/responses.ts | 1 + src/swagger/tariff/index.ts | 5 +- src/swagger/tariff/inputs.ts | 34 +++++----- src/swagger/tariff/models.ts | 9 ++- src/swagger/tariff/responses.ts | 1 + src/types/messages/tariff-message.type.ts | 3 +- src/types/swagger.type.ts | 6 +- src/utils/swagger-error.ts | 1 + 35 files changed, 367 insertions(+), 249 deletions(-) create mode 100644 docs/server/README.md create mode 100644 docs/swagger/README.md diff --git a/.env.example b/.env.example index 6ea1f71..4007686 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ HTTP_HOST=localhost HTTP_PORT=8080 # Auth service -AUTH_SERVICE_HOST=localhost +AUTH_SERVICE_HOST=http://localhost AUTH_SERVICE_PORT=8081 # Database Options diff --git a/README.md b/README.md index edd977d..15eed8d 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,5 @@ # Ссылки на другую документацию: - [**Переменные окружения сервиса**](./docs/environment/README.md) +- [**Swagger**](./docs/swagger/README.md) +- [**Настройки сервера**](./docs/server/README.md) diff --git a/docs/server/README.md b/docs/server/README.md new file mode 100644 index 0000000..bb07102 --- /dev/null +++ b/docs/server/README.md @@ -0,0 +1,14 @@ +# Настройки сервера: + +В папке `./src/constants` лежат два файла конфигурации: + +- `configurations.ts` +- `default.ts` + +**configurations.ts** + +Хранит в себе конфигурации сервиса, загружаемые через `ENV`. В основном хранит в себе данные о подключении к БД и адреса прослушивания запросов сервером. + +**default.ts** + +Хранит в себе стандартные значения и настройки подключаемых модулей, такие как: `fastify`, `swagger`. diff --git a/docs/swagger/README.md b/docs/swagger/README.md new file mode 100644 index 0000000..ca03bcd --- /dev/null +++ b/docs/swagger/README.md @@ -0,0 +1,38 @@ +# Swagger: + +При запуске сервиса, документацию можно будет изучить по отдельной ссылке, которая выдаётся сервисом + +**Ссылка на Swagger документацию**: `/swagger` + +## Архитектура Swagger документации (Для разработчиков): + +``` +├── privilege +│ ├── index.ts +│ ├── inputs.ts +│ ├── models.ts +│ ├── responses.ts +├── tariff +│ ├── index.ts +│ ├── inputs.ts +│ ├── models.ts +│ ├── responses.ts +├── role +│ ├── index.ts +│ ├── inputs.ts +│ ├── models.ts +│ ├── responses.ts +├── account +│ ├── index.ts +│ ├── inputs.ts +│ ├── models.ts +│ ├── responses.ts +``` + +- Каждая из директорий является названием группы обработчиков (`routes`) + +- `index` выдаёт все схемы для генерации документации + +- `inputs` содержит в себе все входные данные, требуемые обработчику: _`params`_, _`querystring`_, _`body`_ + +- `models` хранит в себе все бизнес модели: _`user`_, _`tariff`_, _`role`_ diff --git a/src/configuration/register-fastify-plugins.ts b/src/configuration/register-fastify-plugins.ts index 696d72a..110d270 100644 --- a/src/configuration/register-fastify-plugins.ts +++ b/src/configuration/register-fastify-plugins.ts @@ -5,7 +5,8 @@ import swagger from "@fastify/swagger"; import swaggerUI from "@fastify/swagger-ui"; import printRoutes from "@/plugins/print-routes"; -import { CONFIGURATION } from "@/constants/configuration"; + +import { DEFAULT } from "@/constants/default"; import type { FastifyInstance } from "fastify"; import type { PluginsOptions } from "@/types/configuration/plugins-options"; @@ -14,33 +15,8 @@ export const registerFastifyPlugins = (fastify: FastifyInstance, options: Plugin fastify.register(cors, options.cors); fastify.register(cookie, options.cookie); fastify.register(jwt, options.jwt); - fastify.register(swagger, { - openapi: { - info: { - title: "Hub admin backend", - description: "Тестирование сервиса админ панели хаба", - version: "0.1.0", - }, - servers: [{ url: `http://${CONFIGURATION.http.host || "localhost"}:${CONFIGURATION.http.port}` }], - components: { - securitySchemes: { - bearer: { - type: "http", - bearerFormat: "JWT", - scheme: "bearer", - }, - }, - }, - }, - hideUntagged: true, - }); - fastify.register(swaggerUI, { - routePrefix: "/swagger", - uiConfig: { - docExpansion: "full", - deepLinking: false, - }, - }); + fastify.register(swagger, DEFAULT.swaggerOptions); + fastify.register(swaggerUI, DEFAULT.swaggerUIOptions); fastify.register(printRoutes); return fastify; diff --git a/src/constants/default.ts b/src/constants/default.ts index 2bfa8bb..a0673c2 100644 --- a/src/constants/default.ts +++ b/src/constants/default.ts @@ -1,10 +1,46 @@ -import type { Environment } from "@/types/environment"; +import type { FastifyServerOptions } from "fastify"; +import type { FastifyDynamicSwaggerOptions } from "@fastify/swagger"; +import type { FastifySwaggerUiOptions } from "@fastify/swagger-ui"; type Default = { - readonly [key: string]: string; - readonly environment: Environment; + readonly fastifyOptions: FastifyServerOptions; + readonly swaggerOptions: FastifyDynamicSwaggerOptions; + readonly swaggerUIOptions: FastifySwaggerUiOptions; }; export const DEFAULT: Default = { - environment: "development", + 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 ", + type: "http", + bearerFormat: "JWT", + scheme: "bearer", + }, + }, + }, + }, + hideUntagged: true, + }, + swaggerUIOptions: { + routePrefix: "/swagger", + uiConfig: { + docExpansion: "full", + deepLinking: false, + }, + }, }; diff --git a/src/handlers/account/index.ts b/src/handlers/account/index.ts index febfa41..5dea59c 100644 --- a/src/handlers/account/index.ts +++ b/src/handlers/account/index.ts @@ -1,12 +1,15 @@ import { Types } from "mongoose"; import { AccountModel } from "@/models/account.model"; +import { RoleModel } from "@/models/role.model"; import { getUser } from "@/clients/auth"; import { validateEmptyFields } from "@/utils/validate-empty-fields"; import type { FastifyReply, FastifyRequest } from "fastify"; -import type { GetAccountRequest } from "./types"; +import type { GetAccountRequest, SetAccountRoleRequest } from "./types"; + +export const getAllAccounts = async () => AccountModel.find({}).lean(); export const createAccount = async (request: FastifyRequest, reply: FastifyReply) => { if (!Types.ObjectId.isValid(request.user.id)) { @@ -14,7 +17,7 @@ export const createAccount = async (request: FastifyRequest, reply: FastifyReply return new Error("invalid user id"); } - const account = await AccountModel.findById(request.user.id).lean(); + const account = await AccountModel.findOne({ userId: request.user.id }).lean(); if (account) { reply.status(409); @@ -36,17 +39,45 @@ export const createAccount = async (request: FastifyRequest, reply: FastifyReply }; export const getAccount = async (request: GetAccountRequest, reply: FastifyReply) => { - const [getAccountRequestBody, error] = validateEmptyFields(request.params || {}, ["userId"]); + const [getAccountRequestParams, error] = validateEmptyFields(request.params || {}, ["userId"]); - if (!getAccountRequestBody || error) { + if (error) { reply.status(400); return error; } - if (!Types.ObjectId.isValid(getAccountRequestBody.userId)) { + if (!Types.ObjectId.isValid(getAccountRequestParams.userId)) { reply.status(400); return new Error("invalid user id"); } - return AccountModel.findById(getAccountRequestBody.userId).lean(); + return AccountModel.findOne({ userId: getAccountRequestParams.userId }).lean(); +}; + +export const setAccountRole = async (request: SetAccountRoleRequest, reply: FastifyReply) => { + const [setAccountRoleBody, error] = validateEmptyFields(request.body || {}, ["userId", "role"]); + + if (error) { + reply.status(400); + return error; + } + + const role = await RoleModel.findOne({ name: setAccountRoleBody.role }).lean(); + + if (!role) { + reply.status(404); + return new Error("role not found"); + } + + const account = await AccountModel.findOneAndUpdate( + { userId: setAccountRoleBody.userId }, + { $set: { role: role.name } } + ); + + if (!account) { + reply.status(404); + return new Error("account not found"); + } + + return account; }; diff --git a/src/handlers/account/types.ts b/src/handlers/account/types.ts index 1cd05c0..23aedb5 100644 --- a/src/handlers/account/types.ts +++ b/src/handlers/account/types.ts @@ -5,3 +5,10 @@ export type GetAccountRequest = FastifyRequest<{ userId?: string; }; }>; + +export type SetAccountRoleRequest = FastifyRequest<{ + Body: { + userId?: string; + role?: string; + }; +}>; diff --git a/src/handlers/privilege/helpers.ts b/src/handlers/privilege/helpers.ts index 2a8fa1f..54e76b0 100644 --- a/src/handlers/privilege/helpers.ts +++ b/src/handlers/privilege/helpers.ts @@ -1,5 +1,5 @@ import type { Privilege } from "@/types/models/privilege.type"; -import type { GetAllPrivilegiesFormat, RawPrivilege } from "./types"; +import type { RawPrivilege } from "./types"; export const validatePrivilege = (privilege: RawPrivilege): Error | null => { const typeValues: typeof privilege.type[] = ["count", "day", "full"]; @@ -15,21 +15,6 @@ export const validatePrivilege = (privilege: RawPrivilege): Error | null => { return null; }; -export const defineGetAllPrivilegiesFormat = (query?: string): GetAllPrivilegiesFormat => { - if (!query) { - return "array"; - } - - const formats: GetAllPrivilegiesFormat[] = ["array", "map"]; - const findedFormat = formats.find((format) => query === format); - - if (!findedFormat) { - return "array"; - } - - return findedFormat; -}; - export const convertPrivilegiesToMap = (privilegies: Privilege[]) => { return privilegies.reduce>((accamulator, privilege) => { if (!accamulator[privilege.serviceKey]) { diff --git a/src/handlers/privilege/index.ts b/src/handlers/privilege/index.ts index b9e816b..9fa82d4 100644 --- a/src/handlers/privilege/index.ts +++ b/src/handlers/privilege/index.ts @@ -3,11 +3,10 @@ import { Types } from "mongoose"; import { PrivilegeModel } from "@/models/privilege.model"; import { validateEmptyFields } from "@/utils/validate-empty-fields"; -import { convertPrivilegiesToMap, defineGetAllPrivilegiesFormat, validatePrivilege } from "./helpers"; +import { convertPrivilegiesToMap, validatePrivilege } from "./helpers"; import type { FastifyReply } from "fastify"; import type { - GetAllPrivilegiesRequest, GetServicePrivilegiesRequest, RegisterPrivilegeRequest, GetPrivilegeRequest, @@ -100,25 +99,16 @@ export const registerPrivilege = async (request: RegisterPrivilegeRequest, reply return newPrivilege.save(); }; -export const getAllPrivilegies = async (request: GetAllPrivilegiesRequest, reply: FastifyReply) => { - const format = defineGetAllPrivilegiesFormat(request.query?.format); - const privilegies = await PrivilegeModel.find({}).lean(); +export const getAllPrivilegies = async () => PrivilegeModel.find({ isDeleted: false }).lean(); - if (format === "array") { - return privilegies; - } +export const getAllPrivilegiesMap = async () => { + const privilegies = await PrivilegeModel.find({ isDeleted: false }).lean(); - if (format === "map" && !privilegies.length) { + if (!privilegies.length) { return {}; } - if (format === "map" && privilegies.length) { - return convertPrivilegiesToMap(privilegies); - } - - reply.status(500); - - return new Error("format not defined"); + return convertPrivilegiesToMap(privilegies); }; export const getServicePrivilegies = async (request: GetServicePrivilegiesRequest, reply: FastifyReply) => { @@ -129,23 +119,23 @@ export const getServicePrivilegies = async (request: GetServicePrivilegiesReques return error; } - return PrivilegeModel.find({ serviceKey: requestParams.serviceKey }); + return PrivilegeModel.find({ serviceKey: requestParams.serviceKey, isDeleted: false }).lean(); }; export const getPrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => { - const [requestParams, error] = validateEmptyFields(request.params || {}, ["id"]); + const [requestParams, error] = validateEmptyFields(request.params || {}, ["privilegeId"]); if (error) { reply.status(400); return error; } - if (!Types.ObjectId.isValid(requestParams.id)) { + if (!Types.ObjectId.isValid(requestParams.privilegeId)) { reply.status(400); return new Error("invalid id"); } - const privilege = await PrivilegeModel.findOne({ privilegeId: requestParams.id }); + const privilege = await PrivilegeModel.findOne({ privilegeId: requestParams.privilegeId, isDeleted: false }).lean(); if (!privilege) { reply.status(404); @@ -221,17 +211,15 @@ export const replacePrivilegies = async (request: RegisterPrivilegiesRequest, re await PrivilegeModel.replaceOne( { 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, - updatedAt: new Date(), - isDeleted: false, - }, + 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(); @@ -242,20 +230,20 @@ export const replacePrivilegies = async (request: RegisterPrivilegiesRequest, re }; export const removePrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => { - const [requestParams, error] = validateEmptyFields(request.params || {}, ["id"]); + const [requestParams, error] = validateEmptyFields(request.params || {}, ["privilegeId"]); if (error) { reply.status(400); return error; } - if (!Types.ObjectId.isValid(requestParams.id)) { + if (!Types.ObjectId.isValid(requestParams.privilegeId)) { reply.status(400); return new Error("invalid id"); } const privilege = await PrivilegeModel.findOneAndUpdate( - { privilegeId: requestParams.id }, + { privilegeId: requestParams.privilegeId }, { $set: { isDeleted: true, deletedAt: new Date() } } ); diff --git a/src/handlers/privilege/types.ts b/src/handlers/privilege/types.ts index 97edaf0..4d9d533 100644 --- a/src/handlers/privilege/types.ts +++ b/src/handlers/privilege/types.ts @@ -10,14 +10,6 @@ export type RawPrivilege = { price?: number; }; -export type GetAllPrivilegiesFormat = "array" | "map"; - -export type GetAllPrivilegiesRequest = FastifyRequest<{ - Querystring?: { - format?: GetAllPrivilegiesFormat; - }; -}>; - export type RegisterPrivilegeRequest = FastifyRequest<{ Body?: RawPrivilege; }>; @@ -36,6 +28,6 @@ export type GetServicePrivilegiesRequest = FastifyRequest<{ export type GetPrivilegeRequest = FastifyRequest<{ Params?: { - id?: string; + privilegeId?: string; }; }>; diff --git a/src/handlers/tariff/helpers.ts b/src/handlers/tariff/helpers.ts index b5ae578..a1f5232 100644 --- a/src/handlers/tariff/helpers.ts +++ b/src/handlers/tariff/helpers.ts @@ -3,10 +3,10 @@ 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, Error | null] => { +export const validateTariff = (tariff?: TariffMessage): [ObjectWithRequiredFields, Error | null] => { const [validatedTariff, errorEmpty] = validateEmptyFields( - tariff, - ["isCustom", "name", "price", "privilegies"], + tariff || {}, + ["isCustom", "name", "price", "privilegieIDArray"], false ); diff --git a/src/handlers/tariff/index.ts b/src/handlers/tariff/index.ts index dae2b8f..c5a2a92 100644 --- a/src/handlers/tariff/index.ts +++ b/src/handlers/tariff/index.ts @@ -37,24 +37,30 @@ export const getTariff = async (request: GetTariffRequest, reply: FastifyReply) }; export const createTariff = async (request: CreateTariffRequest, reply: FastifyReply) => { - const [requestBody, error] = validateTariff(request.body || {}); + const [requestBody, error] = validateTariff(request.body); if (error) { reply.status(400); return error; } - for (const privilege of requestBody.privilegies) { - const findedPrivilege = await PrivilegeModel.findOne({ privilegeId: privilege.privilegeId }).lean(); - - if (!findedPrivilege) { + for (const privilegeId of requestBody.privilegieIDArray) { + if (!Types.ObjectId.isValid(privilegeId)) { reply.status(404); - return new Error(`privilege with id ${privilege.privilegeId} not found`); + return new Error(`privilege id <${privilegeId}> invalid`); } } - const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { - accamulator[privilege.privilegeId] = privilege; + const privilegies = await PrivilegeModel.find({ privilegeId: requestBody.privilegieIDArray }).lean(); + + const privilegiesMap = requestBody.privilegieIDArray.reduce>((accamulator, privilegeId) => { + const findedPrivilege = privilegies.find((privilege) => privilege.privilegeId === privilegeId); + + if (!findedPrivilege) { + return accamulator; + } + + accamulator[privilegeId] = findedPrivilege; return accamulator; }, {}); @@ -91,21 +97,23 @@ export const replaceTariff = async (request: ReplaceTariffRequest, reply: Fastif return new Error("tariff not found"); } - for (const privilege of requestBody.privilegies) { - const findedPrivilege = await PrivilegeModel.findOne({ privilegeId: privilege.privilegeId }).lean(); - - if (!findedPrivilege) { + for (const privilegeId of requestBody.privilegieIDArray) { + if (!Types.ObjectId.isValid(privilegeId)) { reply.status(404); - return new Error(`privilege with id ${privilege.privilegeId} not found`); + return new Error(`privilege id <${privilegeId}> invalid`); } } - const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { - if (!accamulator[privilege.privilegeId]) { - accamulator[privilege.privilegeId] = []; + const privilegies = await PrivilegeModel.find({ privilegeId: requestBody.privilegieIDArray }).lean(); + + const privilegiesMap = requestBody.privilegieIDArray.reduce>((accamulator, privilegeId) => { + const findedPrivilege = privilegies.find((privilege) => privilege.privilegeId === privilegeId); + + if (!findedPrivilege) { + return accamulator; } - accamulator[privilege.privilegeId].push(privilege); + accamulator[privilegeId] = findedPrivilege; return accamulator; }, {}); diff --git a/src/routes/account.routes.ts b/src/routes/account.routes.ts index 4999865..355bcaa 100644 --- a/src/routes/account.routes.ts +++ b/src/routes/account.routes.ts @@ -1,11 +1,13 @@ import { Router } from "@/server/router"; -import { createAccount, getAccount } from "@/handlers/account"; +import { createAccount, getAccount, setAccountRole, getAllAccounts } from "@/handlers/account"; import { verifyUser } from "@/handlers/auth/middleware"; -import { createAccountSchema, getAccountSchema } from "@/swagger/account"; +import { createAccountSchema, getAccountSchema, setAccountRoleSchema, getAccountsSchema } from "@/swagger/account"; export const setAccountRoutes = (router: Router): void => { + router.get("/", getAllAccounts, { schema: getAccountsSchema }); router.get("/:userId", getAccount, { schema: getAccountSchema }); router.post("/", createAccount, { preHandler: [verifyUser], schema: createAccountSchema }); + router.post("/role", setAccountRole, { preHandler: [verifyUser], schema: setAccountRoleSchema }); }; diff --git a/src/routes/privilege.routes.ts b/src/routes/privilege.routes.ts index e9fb140..3be9da2 100644 --- a/src/routes/privilege.routes.ts +++ b/src/routes/privilege.routes.ts @@ -3,6 +3,7 @@ import { Router } from "@/server/router"; import { registerPrivilege, getAllPrivilegies, + getAllPrivilegiesMap, getPrivilege, getServicePrivilegies, replacePrivilege, @@ -13,6 +14,7 @@ import { import { getPrivilegiesSchema, + getPrivilegiesMapSchema, getPrivilegeSchema, getServicePrivilegiesSchema, registerPrivilegeSchema, @@ -24,7 +26,8 @@ import { export const setPrivilegeRoutes = (router: Router): void => { router.get("/", getAllPrivilegies, { schema: getPrivilegiesSchema }); - router.get("/:id", getPrivilege, { schema: getPrivilegeSchema }); + 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 }); diff --git a/src/routes/tariff.routes.ts b/src/routes/tariff.routes.ts index 35bdfe0..96946ce 100644 --- a/src/routes/tariff.routes.ts +++ b/src/routes/tariff.routes.ts @@ -9,5 +9,5 @@ export const setTariffRoutes = (router: Router): void => { router.get("/:id", getTariff, { schema: getTariffSchema }); router.get("/", getTariffs, { schema: getTariffsSchema }); router.post("/", createTariff, { preHandler: [verifyUser], schema: createTariffsSchema }); - router.put("/", replaceTariff, { preHandler: [verifyUser], schema: replaceTariffsSchema }); + router.put("/:id", replaceTariff, { preHandler: [verifyUser], schema: replaceTariffsSchema }); }; diff --git a/src/server/index.ts b/src/server/index.ts index 33511fa..0d45736 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -7,6 +7,8 @@ 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"; @@ -36,10 +38,7 @@ export class Server { constructor({ serverOptions, databaseOptions, pluginsOptions }: ServerArgs) { this.serverOptions = serverOptions; this.databaseOptions = databaseOptions; - this.fastify = fastify({ - logger: false, - bodyLimit: 30 * 1024 * 1024, - }); + this.fastify = fastify(DEFAULT.fastifyOptions); if (pluginsOptions) { registerFastifyPlugins(this.fastify, pluginsOptions); @@ -59,6 +58,4 @@ export class Server { }) .catch((reason) => console.error(reason)); }; - - public getFastifyInstance = () => this.fastify; } diff --git a/src/server/router.ts b/src/server/router.ts index 6ddb5a0..7fcebdb 100644 --- a/src/server/router.ts +++ b/src/server/router.ts @@ -38,7 +38,7 @@ export class Router { public get = ( path: string, handler: HandlerMethod, - options: RouteShorthandOptions + options: RouteShorthandOptions = {} ) => { this.fastifyInstance.get(path, options, handler); }; diff --git a/src/swagger/account/index.ts b/src/swagger/account/index.ts index ea8500b..0cc8b1a 100644 --- a/src/swagger/account/index.ts +++ b/src/swagger/account/index.ts @@ -1,8 +1,15 @@ -import { getAccountParams } from "./inputs"; -import { getAccountResponse, createAccountResponse } from "./responses"; +import { getAccountParams, setAccountRoleBody } from "./inputs"; +import { getAccountResponse, createAccountResponse, setAccountRoleResponse, getAccountsResponse } from "./responses"; import type { SwaggerSchema } from "@/types/swagger.type"; +export const getAccountsSchema: SwaggerSchema = { + summary: "Получение информации об аккаунтах", + description: "Получение всех аккаунтов из БД", + tags: ["account"], + response: getAccountsResponse, +}; + export const getAccountSchema: SwaggerSchema = { summary: "Получение информации об аккаунте", description: "Получение аккаунта по ID", @@ -17,3 +24,11 @@ export const createAccountSchema: SwaggerSchema = { response: createAccountResponse, security: [{ bearer: [] }], }; + +export const setAccountRoleSchema: SwaggerSchema = { + summary: "Присвоение роли пользователя", + tags: ["account"], + body: setAccountRoleBody, + response: setAccountRoleResponse, + security: [{ bearer: [] }], +}; diff --git a/src/swagger/account/inputs.ts b/src/swagger/account/inputs.ts index 200ee87..7393a9c 100644 --- a/src/swagger/account/inputs.ts +++ b/src/swagger/account/inputs.ts @@ -2,6 +2,7 @@ import type { SwaggerMessage } from "@/types/swagger.type"; export const getAccountParams: SwaggerMessage = { type: "object", + required: ["userId"], properties: { userId: { type: "string", @@ -9,3 +10,18 @@ export const getAccountParams: SwaggerMessage = { }, }, }; + +export const setAccountRoleBody: SwaggerMessage = { + type: "object", + required: ["userId", "role"], + properties: { + userId: { + type: "string", + description: "ID пользователя", + }, + role: { + type: "string", + description: "название роли", + }, + }, +}; diff --git a/src/swagger/account/models.ts b/src/swagger/account/models.ts index 5702b8b..40cecb1 100644 --- a/src/swagger/account/models.ts +++ b/src/swagger/account/models.ts @@ -4,6 +4,7 @@ export const account: SwaggerMessage = { description: "Аккаунт", type: "object", properties: { + _id: { type: "string" }, userId: { type: "string" }, nickname: { type: "string" }, avatar: { type: "string" }, @@ -24,6 +25,7 @@ export const account: SwaggerMessage = { }, examples: [ { + _id: "807f1f77bcf81cd799439011", userId: "507f1f77bcf86cd799439011", nickname: "Ivanov Ivan Ivanovich", avatar: "/media/avatar/default-avatar.jpg", @@ -33,6 +35,7 @@ export const account: SwaggerMessage = { updatedAt: "2017-07-21T17:32:28Z", }, { + _id: "807f1f77bcf81cd799439011", userId: "507f1f77bcf86cd799439011", nickname: "Ivanov Ivan Ivanovich", avatar: "/media/avatar/default-avatar.jpg", diff --git a/src/swagger/account/responses.ts b/src/swagger/account/responses.ts index 8235190..8dcf95e 100644 --- a/src/swagger/account/responses.ts +++ b/src/swagger/account/responses.ts @@ -4,6 +4,14 @@ import { account } from "./models"; import type { SwaggerMessage } from "@/types/swagger.type"; +export const getAccountsResponse: Record = { + 200: { + type: "array", + description: "Массив аккаунтов", + items: account, + }, +}; + export const getAccountResponse: Record = { 200: account, 400: swaggerError(400, "invalid user id"), @@ -16,3 +24,10 @@ export const createAccountResponse: Record = { 404: swaggerError(404, "user not found"), 409: swaggerError(409, "account already exist"), }; + +export const setAccountRoleResponse: Record = { + 200: account, + 400: swaggerError(400, "invalid user id"), + 401: swaggerError(401, "invalid token"), + 404: swaggerError(404, "user not found"), +}; diff --git a/src/swagger/privilege/index.ts b/src/swagger/privilege/index.ts index bc84698..d26c44b 100644 --- a/src/swagger/privilege/index.ts +++ b/src/swagger/privilege/index.ts @@ -1,13 +1,8 @@ -import { - privilegeBody, - privilegiesBody, - getPrivilegeParams, - getServicePrivilegiesParams, - getPrivilegiesQuery, -} from "./inputs"; +import { privilegeBody, registerPrivilegiesBody, getPrivilegeParams, getServicePrivilegiesParams } from "./inputs"; import { getPrivilegeReponse, getPrivilegiesReponse, + getAllPrivilegiesMapReponse, getAllPrivilegiesReponse, registerPrivilegeResponse, replacePrivilegeResponse, @@ -20,12 +15,18 @@ import type { SwaggerSchema } from "@/types/swagger.type"; export const getPrivilegiesSchema: SwaggerSchema = { summary: "Получение всех привелегий", - description: "Получение всех привелегий в разном формате", + description: "Получение всех привелегий в виде массива", tags: ["privilege"], - querystring: getPrivilegiesQuery, response: getAllPrivilegiesReponse, }; +export const getPrivilegiesMapSchema: SwaggerSchema = { + summary: "Получение всех привелегий", + description: "Получение всех привелегий в виде объекта ключ-значение, где ключём является serviceKey", + tags: ["privilege"], + response: getAllPrivilegiesMapReponse, +}; + export const getPrivilegeSchema: SwaggerSchema = { summary: "Получение привилегии по ID", tags: ["privilege"], @@ -50,7 +51,7 @@ export const registerPrivilegeSchema: SwaggerSchema = { export const registerPrivilegiesSchema: SwaggerSchema = { summary: "Регистрация привелегий сервиса", tags: ["privilege"], - body: privilegiesBody, + body: registerPrivilegiesBody, response: registerPrivilegiesResponse, }; @@ -64,7 +65,7 @@ export const replacePrivilegeSchema: SwaggerSchema = { export const replacePrivilegiesSchema: SwaggerSchema = { summary: "Замена привилегий сервиса", tags: ["privilege"], - body: privilegiesBody, + body: registerPrivilegiesBody, response: replacePrivilegiesResponse, }; diff --git a/src/swagger/privilege/inputs.ts b/src/swagger/privilege/inputs.ts index 7268306..18317c9 100644 --- a/src/swagger/privilege/inputs.ts +++ b/src/swagger/privilege/inputs.ts @@ -14,7 +14,7 @@ export const privilegeBody: SwaggerMessage = { }, examples: [ { - name: "507f1f77bcf86cd799439011", + name: "Количество попыток использования", privilegeId: "507f1f77bcf86cd799439011", serviceKey: "docx-templater-service", description: "Количество попыток использования", @@ -30,16 +30,23 @@ export const privilegiesBody: SwaggerMessage = { items: privilegeBody, }; +export const registerPrivilegiesBody: SwaggerMessage = { + type: "object", + properties: { + privilegies: privilegiesBody, + }, +}; + export const getPrivilegeParams: SwaggerMessage = { type: "object", - required: ["id"], + required: ["privilegeId"], properties: { - id: { + privilegeId: { type: "string", - description: "ID привилегии", + description: "ID привилегии (privilegeId)", }, }, - examples: [{ id: "507f1f77bcf86cd799439011" }], + examples: [{ privilegeId: "507f1f77bcf86cd799439011" }], }; export const getServicePrivilegiesParams: SwaggerMessage = { @@ -48,20 +55,8 @@ export const getServicePrivilegiesParams: SwaggerMessage = { properties: { serviceKey: { type: "string", - description: "ID привилегии", + description: "Ключ сервиса", }, }, - examples: [{ id: "507f1f77bcf86cd799439011" }], -}; - -export const getPrivilegiesQuery: SwaggerMessage = { - type: "object", - properties: { - format: { - type: "string", - description: - "Есть два формата списка привелегий, в виде массива и в виде объекта ключ-значение, где ключём является ключ сервиса", - }, - }, - examples: [{ format: "array" }, { format: "map" }], + examples: [{ serviceKey: "docx-templater-service" }], }; diff --git a/src/swagger/privilege/models.ts b/src/swagger/privilege/models.ts index 452c247..17edf39 100644 --- a/src/swagger/privilege/models.ts +++ b/src/swagger/privilege/models.ts @@ -2,19 +2,8 @@ import type { SwaggerMessage, SwaggerValueType } from "@/types/swagger.type"; const privilegeExamples: SwaggerValueType[] = [ { - name: "use count", - privilegeId: "507f1f77bcf86cd799439011", - serviceKey: "docx-templater-service", - description: "Количество попыток использования", - type: "count", - value: "200", - price: 12300, - isDeleted: false, - createdAt: "2017-07-21T17:32:28Z", - updatedAt: "2017-07-21T17:32:28Z", - }, - { - name: "use count", + _id: "207f1f67bcf86cd799439011", + name: "Количество попыток использования", privilegeId: "507f1f77bcf86cd799439011", serviceKey: "docx-templater-service", description: "Количество попыток использования", @@ -31,6 +20,7 @@ export const privilege: SwaggerMessage = { type: "object", description: "Привилегия", properties: { + _id: { type: "string" }, name: { type: "string" }, privilegeId: { type: "string" }, serviceKey: { type: "string" }, @@ -54,50 +44,3 @@ export const privilege: SwaggerMessage = { }, examples: privilegeExamples, }; - -export const privilegiesMessage: SwaggerMessage = { - type: "object", - description: "Привилегии", - oneOf: [ - { - type: "array", - items: privilege, - }, - { - type: "object", - additionalProperties: privilege, - }, - ], - examples: [ - privilegeExamples, - { - "docx-templater-service": [ - { - name: "use count", - privilegeId: "507f1f77bcf86cd799439011", - serviceKey: "docx-templater-service", - description: "Количество попыток использования", - type: "count", - value: "200", - price: 12300, - isDeleted: false, - createdAt: "2017-07-21T17:32:28Z", - updatedAt: "2017-07-21T17:32:28Z", - }, - { - name: "use count", - privilegeId: "507f1f77bcf86cd799439011", - serviceKey: "docx-templater-service", - description: "Количество попыток использования", - type: "count", - value: "200", - price: 12300, - isDeleted: true, - createdAt: "2017-07-21T17:32:28Z", - updatedAt: "2019-04-14T15:32:15Z", - deletedAt: "2021-08-17T13:23:44Z", - }, - ], - }, - ], -}; diff --git a/src/swagger/privilege/responses.ts b/src/swagger/privilege/responses.ts index 880baa3..08f07cf 100644 --- a/src/swagger/privilege/responses.ts +++ b/src/swagger/privilege/responses.ts @@ -1,17 +1,53 @@ import { swaggerError } from "@/utils/swagger-error"; -import { privilege, privilegiesMessage } from "./models"; +import { privilege } from "./models"; import type { SwaggerMessage } from "@/types/swagger.type"; export const getAllPrivilegiesReponse: Record = { - 200: privilegiesMessage, - 500: swaggerError(500, "format not defined"), + 200: { + type: "array", + description: "Привилегии", + items: privilege, + }, +}; + +export const getAllPrivilegiesMapReponse: Record = { + 200: { + type: "object", + description: "Привилегии", + properties: { + additionalProperties: { + type: "array", + description: "Привилегии", + items: privilege, + }, + }, + additionalProperties: true, + example: { + "docx-templater-service": [ + { + _id: "63a7b01f1f4117cf861d6d1e", + name: "Количество попыток использования", + privilegeId: "507f1f77bcf86cd799439011", + serviceKey: "docx-templater-service", + description: "Количество попыток использования", + type: "count", + value: "200", + price: 12300, + updatedAt: "2022-12-25T02:47:52.405Z", + isDeleted: false, + createdAt: "2022-12-25T02:47:52.406Z", + }, + ], + }, + }, }; export const getPrivilegiesReponse: Record = { 200: { type: "array", + description: "Массив привилегий", items: privilege, }, }; @@ -31,6 +67,7 @@ export const registerPrivilegeResponse: Record = { export const registerPrivilegiesResponse: Record = { 200: { type: "array", + description: "Массив привилегий", items: privilege, }, 400: swaggerError(400, "price must be a number"), @@ -45,6 +82,7 @@ export const replacePrivilegeResponse: Record = { export const replacePrivilegiesResponse: Record = { 200: { type: "array", + description: "Массив привилегий", items: privilege, }, 400: swaggerError(400, "invalid 'type' value"), diff --git a/src/swagger/role/models.ts b/src/swagger/role/models.ts index 8a79fa7..042c320 100644 --- a/src/swagger/role/models.ts +++ b/src/swagger/role/models.ts @@ -4,6 +4,7 @@ export const role: SwaggerMessage = { type: "object", description: "Роль", properties: { + _id: { type: "string" }, name: { type: "string" }, permissions: { type: "object", @@ -12,6 +13,7 @@ export const role: SwaggerMessage = { }, examples: [ { + _id: "638388e120c70c17eb123d37", name: "user", permissions: { read: true, @@ -19,6 +21,7 @@ export const role: SwaggerMessage = { }, }, { + _id: "638388f720c70c17eb123d3a", name: "admin", permissions: { read: true, diff --git a/src/swagger/role/responses.ts b/src/swagger/role/responses.ts index 2cc43ac..04509b6 100644 --- a/src/swagger/role/responses.ts +++ b/src/swagger/role/responses.ts @@ -7,6 +7,7 @@ import type { SwaggerMessage } from "@/types/swagger.type"; export const getRolesReponse: Record = { 200: { type: "array", + description: "Массив ролей", items: role, }, }; diff --git a/src/swagger/tariff/index.ts b/src/swagger/tariff/index.ts index 087a9a9..6684998 100644 --- a/src/swagger/tariff/index.ts +++ b/src/swagger/tariff/index.ts @@ -1,4 +1,4 @@ -import { getTariffParams, tariffBody } from "./inputs"; +import { getTariffParams, tariffBody, replaceTariffParams } from "./inputs"; import { getTariffReponse, getTariffsReponse, createTariffReponse, replaceTariffReponse } from "./responses"; import type { SwaggerSchema } from "@/types/swagger.type"; @@ -20,11 +20,14 @@ export const createTariffsSchema: SwaggerSchema = { summary: "Создание тарифа", tags: ["tariff"], body: tariffBody, + security: [{ bearer: [] }], response: createTariffReponse, }; export const replaceTariffsSchema: SwaggerSchema = { summary: "Замена тарифа", tags: ["tariff"], + params: replaceTariffParams, body: tariffBody, + security: [{ bearer: [] }], response: replaceTariffReponse, }; diff --git a/src/swagger/tariff/inputs.ts b/src/swagger/tariff/inputs.ts index c2d66fa..e03a43f 100644 --- a/src/swagger/tariff/inputs.ts +++ b/src/swagger/tariff/inputs.ts @@ -1,5 +1,3 @@ -import { privilege } from "@/swagger/privilege/models"; - import type { SwaggerMessage } from "@/types/swagger.type"; export const getTariffParams: SwaggerMessage = { @@ -17,32 +15,34 @@ export const getTariffParams: SwaggerMessage = { export const tariffBody: SwaggerMessage = { type: "object", description: "Тариф", - required: ["name", "price", "isCustom", "privilegies"], + required: ["name", "price", "isCustom", "privilegieIDArray"], properties: { name: { type: "string" }, price: { type: "number" }, isCustom: { type: "boolean" }, - privilegies: { + privilegieIDArray: { type: "array", - items: privilege, + items: { type: "string" }, }, }, examples: [ { - name: "user", + name: "Использование сервисов", price: 14000, isCustom: false, - privilegies: [ - { - name: "507f1f77bcf86cd799439011", - privilegeId: "507f1f77bcf86cd799439011", - serviceKey: "docx-templater-service", - description: "Количество попыток использования", - type: "count", - value: "200", - price: 12300, - }, - ], + privilegieIDArray: ["507f1f77bcf86cd799439011"], }, ], }; + +export const replaceTariffParams: SwaggerMessage = { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + description: "ID тарифа", + }, + }, + examples: [{ id: "63a7d47ba24613f98562bafa" }], +}; diff --git a/src/swagger/tariff/models.ts b/src/swagger/tariff/models.ts index 93e4d95..cfd4ee4 100644 --- a/src/swagger/tariff/models.ts +++ b/src/swagger/tariff/models.ts @@ -4,6 +4,7 @@ const privilege: SwaggerMessage = { type: "object", description: "Привилегия", properties: { + _id: { type: "string" }, name: { type: "string" }, privilegeId: { type: "string" }, serviceKey: { type: "string" }, @@ -14,10 +15,11 @@ const privilege: SwaggerMessage = { }, examples: [ { - name: "507f1f77bcf86cd799439011", + _id: "507f1f77bcf86cd799439011", + name: "Количество попыток использования", privilegeId: "507f1f77bcf86cd799439011", serviceKey: "docx-templater-service", - description: "Количество попыток использования", + description: "Количество попыток использования сервиса генерации шаблонов", type: "count", value: "200", price: 12300, @@ -29,6 +31,7 @@ export const tariff: SwaggerMessage = { type: "object", description: "Тариф", properties: { + _id: { type: "string" }, name: { type: "string" }, price: { type: "number" }, isCustom: { type: "boolean" }, @@ -52,7 +55,7 @@ export const tariff: SwaggerMessage = { }, examples: [ { - name: "user", + name: "Использование сервисов", price: 14000, isCustom: false, privilegies: { diff --git a/src/swagger/tariff/responses.ts b/src/swagger/tariff/responses.ts index d4d7b23..c2039a2 100644 --- a/src/swagger/tariff/responses.ts +++ b/src/swagger/tariff/responses.ts @@ -13,6 +13,7 @@ export const getTariffReponse: Record = { export const getTariffsReponse: Record = { 200: { type: "array", + description: "Массив тарифов", items: tariff, }, }; diff --git a/src/types/messages/tariff-message.type.ts b/src/types/messages/tariff-message.type.ts index ccce19e..63ad826 100644 --- a/src/types/messages/tariff-message.type.ts +++ b/src/types/messages/tariff-message.type.ts @@ -1,10 +1,9 @@ import type { Eloquent } from "../models/eloquent.type"; -import type { Privilege } from "../models/privilege.type"; import type { Tariff } from "../models/tariff.type"; import type { ObjectWithPossibleFields } from "../object-with-possible-fields"; export type TariffMessage = ObjectWithPossibleFields< Omit & { - privilegies: Privilege[]; + privilegieIDArray: string[]; } >; diff --git a/src/types/swagger.type.ts b/src/types/swagger.type.ts index d355731..15a88d5 100644 --- a/src/types/swagger.type.ts +++ b/src/types/swagger.type.ts @@ -24,7 +24,7 @@ type SwaggerMediaFormat = | "ipv6"; export type SwaggerMessage = { - type: SwaggerMediaType; + type?: SwaggerMediaType; description?: string; format?: SwaggerMediaFormat; pattern?: string; @@ -33,9 +33,10 @@ export type SwaggerMessage = { writeOnly?: boolean; readOnly?: boolean; items?: SwaggerMessage; - additionalProperties?: SwaggerMessage; + additionalProperties?: SwaggerMessage | boolean; properties?: Record>; examples?: SwaggerValueType[]; + example?: SwaggerValueType; required?: string[]; oneOf?: SwaggerMessage[]; allOf?: SwaggerMessage[]; @@ -44,5 +45,6 @@ export type SwaggerMessage = { export type SwaggerSchema = FastifySchema & { params?: SwaggerMessage; body?: SwaggerMessage; + querystring?: SwaggerMessage; response?: Record; }; diff --git a/src/utils/swagger-error.ts b/src/utils/swagger-error.ts index 9587fa4..7cbd5e2 100644 --- a/src/utils/swagger-error.ts +++ b/src/utils/swagger-error.ts @@ -10,6 +10,7 @@ const STATUS_CODE_MAP: Record = { export const swaggerError = (code: number, message: string): SwaggerMessage => ({ type: "object", + description: STATUS_CODE_MAP[code], properties: { statusCode: { type: "integer" }, error: { type: "string" }, From e9b02dea08bffb6f84f29eb2c78949e599cca2b6 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sun, 25 Dec 2022 20:28:47 +0000 Subject: [PATCH 08/10] feat(tariff/privilege): remove handlers & docs --- src/handlers/account/index.ts | 54 +++++++++++++++++++++++ src/handlers/privilege/index.ts | 58 +++++++++++++++++++++++-- src/handlers/privilege/types.ts | 6 +++ src/handlers/tariff/index.ts | 73 +++++++++++++++++++++++++++++++- src/handlers/tariff/types.ts | 6 +++ src/routes/account.routes.ts | 25 +++++++++-- src/routes/privilege.routes.ts | 3 ++ src/routes/tariff.routes.ts | 25 +++++++++-- src/swagger/account/index.ts | 32 +++++++++++++- src/swagger/account/responses.ts | 7 +++ src/swagger/privilege/index.ts | 7 +++ src/swagger/tariff/index.ts | 35 ++++++++++++++- src/swagger/tariff/responses.ts | 9 ++++ 13 files changed, 327 insertions(+), 13 deletions(-) diff --git a/src/handlers/account/index.ts b/src/handlers/account/index.ts index 5dea59c..9999a27 100644 --- a/src/handlers/account/index.ts +++ b/src/handlers/account/index.ts @@ -81,3 +81,57 @@ export const setAccountRole = async (request: SetAccountRoleRequest, reply: Fast return account; }; + +export const removeAccount = async (request: FastifyRequest, reply: FastifyReply) => { + if (!Types.ObjectId.isValid(request.user.id)) { + reply.status(400); + return new Error("invalid user id"); + } + + const account = await AccountModel.findOneAndUpdate( + { userId: request.user.id }, + { $set: { isDeleted: true, deletedAt: new Date() } } + ).lean(); + + if (!account) { + reply.status(404); + return new Error("account not found"); + } + + return account; +}; + +export const deleteAccount = async (request: FastifyRequest, reply: FastifyReply) => { + if (!Types.ObjectId.isValid(request.user.id)) { + reply.status(400); + return new Error("invalid user id"); + } + + const account = await AccountModel.findByIdAndDelete({ userId: request.user.id }).lean(); + + if (!account) { + reply.status(404); + return new Error("account not found"); + } + + return account; +}; + +export const restoreAccount = async (request: FastifyRequest, reply: FastifyReply) => { + if (!Types.ObjectId.isValid(request.user.id)) { + reply.status(400); + return new Error("invalid user id"); + } + + const account = await AccountModel.findOneAndUpdate( + { userId: request.user.id }, + { $set: { isDeleted: false } } + ).lean(); + + if (!account) { + reply.status(404); + return new Error("account not found"); + } + + return account; +}; diff --git a/src/handlers/privilege/index.ts b/src/handlers/privilege/index.ts index 9fa82d4..d57d865 100644 --- a/src/handlers/privilege/index.ts +++ b/src/handlers/privilege/index.ts @@ -11,6 +11,7 @@ import type { RegisterPrivilegeRequest, GetPrivilegeRequest, RegisterPrivilegiesRequest, + RemovePrivilegeRequest, } from "./types"; export const registerPrivilegies = async (request: RegisterPrivilegiesRequest, reply: FastifyReply) => { @@ -182,6 +183,32 @@ export const replacePrivilege = async (request: RegisterPrivilegeRequest, reply: updatedAt: new Date(), }); + return Promise.all(replacePrivilegeRequests); +}; + +export const removePrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => { + const [requestParams, error] = validateEmptyFields(request.params || {}, ["privilegeId"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(requestParams.privilegeId)) { + reply.status(400); + return new Error("invalid id"); + } + + const privilege = await PrivilegeModel.findOneAndUpdate( + { privilegeId: requestParams.privilegeId }, + { $set: { isDeleted: true, deletedAt: new Date() } } + ); + + if (!privilege) { + reply.status(404); + return new Error("privilege not found"); + } + return privilege; }; @@ -229,21 +256,21 @@ export const replacePrivilegies = async (request: RegisterPrivilegiesRequest, re return Promise.all(replacePrivilegeRequests); }; -export const removePrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => { - const [requestParams, error] = validateEmptyFields(request.params || {}, ["privilegeId"]); +export const removePrivilege = async (request: RemovePrivilegeRequest, reply: FastifyReply) => { + const [{ privilegeId }, error] = validateEmptyFields(request.body || {}, ["privilegeId"]); if (error) { reply.status(400); return error; } - if (!Types.ObjectId.isValid(requestParams.privilegeId)) { + if (!Types.ObjectId.isValid(privilegeId)) { reply.status(400); return new Error("invalid id"); } const privilege = await PrivilegeModel.findOneAndUpdate( - { privilegeId: requestParams.privilegeId }, + { privilegeId }, { $set: { isDeleted: true, deletedAt: new Date() } } ); @@ -254,3 +281,26 @@ export const removePrivilege = async (request: GetPrivilegeRequest, reply: Fasti return privilege; }; + +export const restorePrivilege = async (request: RemovePrivilegeRequest, reply: FastifyReply) => { + const [{ privilegeId }, error] = validateEmptyFields(request.body || {}, ["privilegeId"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(privilegeId)) { + reply.status(400); + return new Error("invalid id"); + } + + const privilege = await PrivilegeModel.findOneAndUpdate({ privilegeId }, { $set: { isDeleted: false } }); + + if (!privilege) { + reply.status(404); + return new Error("privilege not found"); + } + + return privilege; +}; diff --git a/src/handlers/privilege/types.ts b/src/handlers/privilege/types.ts index 4d9d533..9d12026 100644 --- a/src/handlers/privilege/types.ts +++ b/src/handlers/privilege/types.ts @@ -31,3 +31,9 @@ export type GetPrivilegeRequest = FastifyRequest<{ privilegeId?: string; }; }>; + +export type RemovePrivilegeRequest = FastifyRequest<{ + Body?: { + privilegeId?: string; + }; +}>; diff --git a/src/handlers/tariff/index.ts b/src/handlers/tariff/index.ts index c5a2a92..575fe25 100644 --- a/src/handlers/tariff/index.ts +++ b/src/handlers/tariff/index.ts @@ -9,7 +9,7 @@ import { validateTariff } from "./helpers"; import type { FastifyReply } from "fastify"; import type { Privilege } from "@/types/models/privilege.type"; -import type { CreateTariffRequest, GetTariffRequest, ReplaceTariffRequest } from "./types"; +import type { CreateTariffRequest, GetTariffRequest, ReplaceTariffRequest, RemoveTariffRequest } from "./types"; export const getTariffs = async () => TariffModel.find({}).lean(); @@ -127,3 +127,74 @@ export const replaceTariff = async (request: ReplaceTariffRequest, reply: Fastif return tariff; }; + +export const removeTariff = async (request: RemoveTariffRequest, reply: FastifyReply) => { + const [{ id }, error] = validateEmptyFields(request.body || {}, ["id"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(id)) { + reply.status(400); + return new Error("invalid id"); + } + + const tariff = await TariffModel.findByIdAndUpdate(id, { + $set: { isDeleted: true, deletedAt: new Date() }, + }).lean(); + + if (!tariff) { + reply.status(404); + return new Error("tariff not found"); + } + + return tariff; +}; + +export const deleteTariff = async (request: RemoveTariffRequest, reply: FastifyReply) => { + const [{ id }, error] = validateEmptyFields(request.body || {}, ["id"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(id)) { + reply.status(400); + return new Error("invalid id"); + } + + const tariff = await TariffModel.findByIdAndDelete(id).lean(); + + if (!tariff) { + reply.status(404); + return new Error("tariff not found"); + } + + return tariff; +}; + +export const restoreTariff = async (request: RemoveTariffRequest, reply: FastifyReply) => { + const [{ id }, error] = validateEmptyFields(request.body || {}, ["id"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(id)) { + reply.status(400); + return new Error("invalid id"); + } + + const tariff = await TariffModel.findByIdAndUpdate(id, { $set: { isDeleted: false } }).lean(); + + if (!tariff) { + reply.status(404); + return new Error("tariff not found"); + } + + return tariff; +}; diff --git a/src/handlers/tariff/types.ts b/src/handlers/tariff/types.ts index b2f73c1..d521405 100644 --- a/src/handlers/tariff/types.ts +++ b/src/handlers/tariff/types.ts @@ -7,6 +7,12 @@ export type GetTariffRequest = FastifyRequest<{ }; }>; +export type RemoveTariffRequest = FastifyRequest<{ + Body?: { + id?: string; + }; +}>; + export type CreateTariffRequest = FastifyRequest<{ Body?: TariffMessage; }>; diff --git a/src/routes/account.routes.ts b/src/routes/account.routes.ts index 355bcaa..fc992c2 100644 --- a/src/routes/account.routes.ts +++ b/src/routes/account.routes.ts @@ -1,13 +1,32 @@ import { Router } from "@/server/router"; -import { createAccount, getAccount, setAccountRole, getAllAccounts } from "@/handlers/account"; +import { + createAccount, + getAccount, + setAccountRole, + getAllAccounts, + deleteAccount, + removeAccount, + restoreAccount, +} from "@/handlers/account"; import { verifyUser } from "@/handlers/auth/middleware"; -import { createAccountSchema, getAccountSchema, setAccountRoleSchema, getAccountsSchema } from "@/swagger/account"; +import { + createAccountSchema, + getAccountSchema, + setAccountRoleSchema, + getAccountsSchema, + removeAccountSchema, + restoreAccountSchema, + deleteAccountSchema, +} from "@/swagger/account"; export const setAccountRoutes = (router: Router): void => { router.get("/", getAllAccounts, { schema: getAccountsSchema }); router.get("/:userId", getAccount, { schema: getAccountSchema }); router.post("/", createAccount, { preHandler: [verifyUser], schema: createAccountSchema }); - router.post("/role", setAccountRole, { preHandler: [verifyUser], schema: setAccountRoleSchema }); + 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("/delete", deleteAccount, { preHandler: [verifyUser], schema: deleteAccountSchema }); }; diff --git a/src/routes/privilege.routes.ts b/src/routes/privilege.routes.ts index 3be9da2..d9dbfa0 100644 --- a/src/routes/privilege.routes.ts +++ b/src/routes/privilege.routes.ts @@ -10,6 +10,7 @@ import { registerPrivilegies, removePrivilege, replacePrivilegies, + restorePrivilege, } from "@/handlers/privilege"; import { @@ -22,6 +23,7 @@ import { replacePrivilegeSchema, replacePrivilegiesSchema, removePrivilegeSchema, + restorePrivilegeSchema, } from "@/swagger/privilege"; export const setPrivilegeRoutes = (router: Router): void => { @@ -31,6 +33,7 @@ export const setPrivilegeRoutes = (router: Router): void => { 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 }); diff --git a/src/routes/tariff.routes.ts b/src/routes/tariff.routes.ts index 96946ce..645af9f 100644 --- a/src/routes/tariff.routes.ts +++ b/src/routes/tariff.routes.ts @@ -1,13 +1,32 @@ import { Router } from "@/server/router"; -import { createTariff, replaceTariff, getTariff, getTariffs } from "@/handlers/tariff"; +import { + createTariff, + replaceTariff, + getTariff, + getTariffs, + removeTariff, + restoreTariff, + deleteTariff, +} from "@/handlers/tariff"; import { verifyUser } from "@/handlers/auth/middleware"; -import { getTariffSchema, getTariffsSchema, createTariffsSchema, replaceTariffsSchema } from "@/swagger/tariff"; +import { + getTariffSchema, + getTariffsSchema, + createTariffsSchema, + replaceTariffsSchema, + removeTariffsSchema, + restoreTariffsSchema, + deleteTariffsSchema, +} from "@/swagger/tariff"; export const setTariffRoutes = (router: Router): void => { - router.get("/:id", getTariff, { schema: getTariffSchema }); router.get("/", getTariffs, { schema: getTariffsSchema }); + router.get("/:id", getTariff, { 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 }); }; diff --git a/src/swagger/account/index.ts b/src/swagger/account/index.ts index 0cc8b1a..cf9730b 100644 --- a/src/swagger/account/index.ts +++ b/src/swagger/account/index.ts @@ -1,5 +1,11 @@ import { getAccountParams, setAccountRoleBody } from "./inputs"; -import { getAccountResponse, createAccountResponse, setAccountRoleResponse, getAccountsResponse } from "./responses"; +import { + getAccountResponse, + createAccountResponse, + setAccountRoleResponse, + getAccountsResponse, + removeRoleResponse, +} from "./responses"; import type { SwaggerSchema } from "@/types/swagger.type"; @@ -32,3 +38,27 @@ export const setAccountRoleSchema: SwaggerSchema = { response: setAccountRoleResponse, security: [{ bearer: [] }], }; + +export const removeAccountSchema: SwaggerSchema = { + summary: "Удаление аккаунта", + description: "Помечает аккаунт удалённым, но не удаляет его из БД", + tags: ["account"], + response: removeRoleResponse, + security: [{ bearer: [] }], +}; + +export const deleteAccountSchema: SwaggerSchema = { + summary: "Удаление аккаунта", + description: "Удаляет аккаунт из БД окончательно", + tags: ["account"], + response: removeRoleResponse, + security: [{ bearer: [] }], +}; + +export const restoreAccountSchema: SwaggerSchema = { + summary: "Восстановление аккаунта", + description: "Восстанавливает аккаунт, который не был удалён окончательно", + tags: ["account"], + response: removeRoleResponse, + security: [{ bearer: [] }], +}; diff --git a/src/swagger/account/responses.ts b/src/swagger/account/responses.ts index 8dcf95e..54ae070 100644 --- a/src/swagger/account/responses.ts +++ b/src/swagger/account/responses.ts @@ -31,3 +31,10 @@ export const setAccountRoleResponse: Record = { 401: swaggerError(401, "invalid token"), 404: swaggerError(404, "user not found"), }; + +export const removeRoleResponse: Record = { + 200: account, + 400: swaggerError(400, "invalid user id"), + 401: swaggerError(401, "invalid token"), + 404: swaggerError(404, "user not found"), +}; diff --git a/src/swagger/privilege/index.ts b/src/swagger/privilege/index.ts index d26c44b..caaf5fd 100644 --- a/src/swagger/privilege/index.ts +++ b/src/swagger/privilege/index.ts @@ -75,3 +75,10 @@ export const removePrivilegeSchema: SwaggerSchema = { body: getPrivilegeParams, response: removePrivilegeResponse, }; + +export const restorePrivilegeSchema: SwaggerSchema = { + summary: "Восстановление привилегии", + tags: ["privilege"], + body: getPrivilegeParams, + response: removePrivilegeResponse, +}; diff --git a/src/swagger/tariff/index.ts b/src/swagger/tariff/index.ts index 6684998..0d8de50 100644 --- a/src/swagger/tariff/index.ts +++ b/src/swagger/tariff/index.ts @@ -1,5 +1,11 @@ import { getTariffParams, tariffBody, replaceTariffParams } from "./inputs"; -import { getTariffReponse, getTariffsReponse, createTariffReponse, replaceTariffReponse } from "./responses"; +import { + getTariffReponse, + getTariffsReponse, + createTariffReponse, + replaceTariffReponse, + removeTariffReponse, +} from "./responses"; import type { SwaggerSchema } from "@/types/swagger.type"; @@ -23,6 +29,7 @@ export const createTariffsSchema: SwaggerSchema = { security: [{ bearer: [] }], response: createTariffReponse, }; + export const replaceTariffsSchema: SwaggerSchema = { summary: "Замена тарифа", tags: ["tariff"], @@ -31,3 +38,29 @@ export const replaceTariffsSchema: SwaggerSchema = { security: [{ bearer: [] }], response: replaceTariffReponse, }; + +export const removeTariffsSchema: SwaggerSchema = { + summary: "Удаление тарифа", + description: "Помечает тариф удалённым, но не удаляет его из БД", + tags: ["tariff"], + body: getTariffParams, + security: [{ bearer: [] }], + response: removeTariffReponse, +}; + +export const deleteTariffsSchema: SwaggerSchema = { + summary: "Удаление тарифа", + description: "Удаляет тариф из БД окончательно", + tags: ["tariff"], + body: getTariffParams, + security: [{ bearer: [] }], + response: removeTariffReponse, +}; + +export const restoreTariffsSchema: SwaggerSchema = { + summary: "Восстановление тарифа", + tags: ["tariff"], + body: getTariffParams, + security: [{ bearer: [] }], + response: removeTariffReponse, +}; diff --git a/src/swagger/tariff/responses.ts b/src/swagger/tariff/responses.ts index c2039a2..ead2bc7 100644 --- a/src/swagger/tariff/responses.ts +++ b/src/swagger/tariff/responses.ts @@ -21,11 +21,20 @@ export const getTariffsReponse: Record = { export const createTariffReponse: Record = { 200: tariff, 400: swaggerError(400, "invalid 'price' value"), + 401: swaggerError(400, "invalid user id"), 404: swaggerError(404, "privilege with id not found"), }; export const replaceTariffReponse: Record = { 200: tariff, 400: swaggerError(400, "invalid id"), + 401: swaggerError(400, "invalid user id"), + 404: swaggerError(404, "tariff not found"), +}; + +export const removeTariffReponse: Record = { + 200: tariff, + 400: swaggerError(400, "invalid id"), + 401: swaggerError(400, "invalid user id"), 404: swaggerError(404, "tariff not found"), }; From 28ae2740511785acabef0c18e86a5b0ea4987db9 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 10 Jan 2023 12:26:59 +0000 Subject: [PATCH 09/10] Delete handlers --- README.md | 13 +----------- docs/environment/README.md | 35 --------------------------------- src/handlers/privilege/index.ts | 31 +++++++++++++++++++++++++---- 3 files changed, 28 insertions(+), 51 deletions(-) delete mode 100644 docs/environment/README.md diff --git a/README.md b/README.md index 15eed8d..8ea2065 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,3 @@ # Hub Admin Panel Backend Service -## Настройка и запуск - -``` -1) yarn setup - загрузка всех зависимостей и инициализация husky -2) yarn dev - запуск проекта в режиме разработки -``` - -# Ссылки на другую документацию: - -- [**Переменные окружения сервиса**](./docs/environment/README.md) -- [**Swagger**](./docs/swagger/README.md) -- [**Настройки сервера**](./docs/server/README.md) +**Вся основная документация расположена в** [**Wiki**](https://penahub.gitlab.yandexcloud.net/pena-services/hub_admin_backend_service/-/wikis/%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5) diff --git a/docs/environment/README.md b/docs/environment/README.md deleted file mode 100644 index afc4b90..0000000 --- a/docs/environment/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Переменные окружения сервиса: - -**Для конфигурации сервера**: - -``` -ENVIRONMENT - application environment -HTTP_HOST - service host -HTTP_PORT - service port -PUBLIC_ACCESS_SECRET_KEY - secret to verify private access secret key -``` - -**Для конфигурации базы данных**: - -``` -DB_HOST - mongo host -DB_PORT - mongo port -DB_USERNAME - mongo username -DB_PASSWORD - mongo password -DB_NAME - database name -``` - -**Для подключения к сервису авторизации** - -``` -AUTH_SERVICE_HOST - auth service host -AUTH_SERVICE_PORT - auth service port -``` - -## Среды окружения - -``` -development - среда для разработки -staging - среда для тестирования продукта -production - среда продакшена -``` diff --git a/src/handlers/privilege/index.ts b/src/handlers/privilege/index.ts index d57d865..aa31f4b 100644 --- a/src/handlers/privilege/index.ts +++ b/src/handlers/privilege/index.ts @@ -186,21 +186,21 @@ export const replacePrivilege = async (request: RegisterPrivilegeRequest, reply: return Promise.all(replacePrivilegeRequests); }; -export const removePrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => { - const [requestParams, error] = validateEmptyFields(request.params || {}, ["privilegeId"]); +export const removePrivilege = async (request: RemovePrivilegeRequest, reply: FastifyReply) => { + const [{ privilegeId }, error] = validateEmptyFields(request.body || {}, ["privilegeId"]); if (error) { reply.status(400); return error; } - if (!Types.ObjectId.isValid(requestParams.privilegeId)) { + if (!Types.ObjectId.isValid(privilegeId)) { reply.status(400); return new Error("invalid id"); } const privilege = await PrivilegeModel.findOneAndUpdate( - { privilegeId: requestParams.privilegeId }, + { privilegeId }, { $set: { isDeleted: true, deletedAt: new Date() } } ); @@ -212,6 +212,29 @@ export const removePrivilege = async (request: GetPrivilegeRequest, reply: Fasti return privilege; }; +export const restorePrivilege = async (request: RemovePrivilegeRequest, reply: FastifyReply) => { + const [{ privilegeId }, error] = validateEmptyFields(request.body || {}, ["privilegeId"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(privilegeId)) { + reply.status(400); + return new Error("invalid id"); + } + + const privilege = await PrivilegeModel.findOneAndUpdate({ privilegeId }, { $set: { isDeleted: false } }); + + if (!privilege) { + 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"]); From 143cd9f7c59bd75b5ea589f23255426c247984cd Mon Sep 17 00:00:00 2001 From: Skeris Date: Fri, 17 Mar 2023 16:05:46 +0300 Subject: [PATCH 10/10] feat: try to run ci\cd --- .gitlab-ci.yml | 3 +++ Dockerfile | 4 ++-- deployments/staging/docker-compose.yaml | 28 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 deployments/staging/docker-compose.yaml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..fa6d0cc --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,3 @@ +include: + - project: "devops/pena-continuous-integration" + file: "/nodejs/docker/nodejs.gitlab-ci.yml" diff --git a/Dockerfile b/Dockerfile index 64b594b..f29dce9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16-alpine as dev +FROM node:19.1-alpine as dev RUN apk update && rm -rf /var/cache/apk/* @@ -17,7 +17,7 @@ RUN ls RUN yarn build -FROM node:16-alpine as production +FROM node:19.1-alpine as production RUN apk update && rm -rf /var/cache/apk/* diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml new file mode 100644 index 0000000..21e9302 --- /dev/null +++ b/deployments/staging/docker-compose.yaml @@ -0,0 +1,28 @@ +version: "3.3" + +services: + admin: + container_name: hub-admin-backend-service + restart: unless-stopped + tty: true + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + expose: + - 8005 + networks: + - marketplace_penahub_frontend + - default + 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=8000 + - 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