Merge branch 'dev' into 'staging'

first deploy

See merge request pena-services/hub_admin_backend_service!10
This commit is contained in:
Mikhail 2023-03-17 13:14:23 +00:00
commit ed8b9970e5
74 changed files with 2697 additions and 349 deletions

@ -2,6 +2,10 @@
HTTP_HOST=localhost HTTP_HOST=localhost
HTTP_PORT=8080 HTTP_PORT=8080
# Auth service
AUTH_SERVICE_HOST=http://localhost
AUTH_SERVICE_PORT=8081
# Database Options # Database Options
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=27017 DB_PORT=27017

@ -130,7 +130,7 @@
"error", "error",
{ {
"props": true, "props": true,
"ignorePropertyModificationsFor": ["accamulator"] "ignorePropertyModificationsFor": ["accamulator", "request"]
} }
], ],
"object-shorthand": "off", "object-shorthand": "off",

3
.gitlab-ci.yml Normal file

@ -0,0 +1,3 @@
include:
- project: "devops/pena-continuous-integration"
file: "/nodejs/docker/nodejs.gitlab-ci.yml"

@ -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/* RUN apk update && rm -rf /var/cache/apk/*
@ -17,7 +17,7 @@ RUN ls
RUN yarn build RUN yarn build
FROM node:16-alpine as production FROM node:19.1-alpine as production
RUN apk update && rm -rf /var/cache/apk/* RUN apk update && rm -rf /var/cache/apk/*

@ -1,37 +1,3 @@
# Hub Admin Panel Backend Service # Hub Admin Panel Backend Service
## Настройка и запуск **Вся основная документация расположена в** [**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)
```
1) yarn setup - загрузка всех зависимостей и инициализация husky
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
```
## Среды окружения
```
development - среда для разработки
staging - среда для тестирования продукта
production - среда продакшена
```

@ -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

14
docs/server/README.md Normal file

@ -0,0 +1,14 @@
# Настройки сервера:
В папке `./src/constants` лежат два файла конфигурации:
- `configurations.ts`
- `default.ts`
**configurations.ts**
Хранит в себе конфигурации сервиса, загружаемые через `ENV`. В основном хранит в себе данные о подключении к БД и адреса прослушивания запросов сервером.
**default.ts**
Хранит в себе стандартные значения и настройки подключаемых модулей, такие как: `fastify`, `swagger`.

38
docs/swagger/README.md Normal file

@ -0,0 +1,38 @@
# Swagger:
При запуске сервиса, документацию можно будет изучить по отдельной ссылке, которая выдаётся сервисом
**Ссылка на Swagger документацию**: `<hostname>/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`_

@ -23,6 +23,7 @@
"@fastify/cookie": "^8.3.0", "@fastify/cookie": "^8.3.0",
"@fastify/cors": "^8.2.0", "@fastify/cors": "^8.2.0",
"@fastify/jwt": "^6.3.3", "@fastify/jwt": "^6.3.3",
"axios": "^1.2.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"fastify": "^4.9.2", "fastify": "^4.9.2",
@ -36,6 +37,8 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.1.2", "@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0", "@commitlint/config-conventional": "^17.1.0",
"@fastify/swagger": "^8.2.1",
"@fastify/swagger-ui": "^1.3.0",
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/jest": "^29.2.3", "@types/jest": "^29.2.3",
"@types/jsonwebtoken": "^8.5.9", "@types/jsonwebtoken": "^8.5.9",

16
src/clients/auth/index.ts Normal file

@ -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<User | null> => {
try {
const { data } = await authService.get<User>(`/user/${request.id}`);
return data;
} catch (nativeError) {
console.error(nativeError);
}
return null;
};

@ -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}`,
});

@ -0,0 +1,3 @@
export type GetUserRequest = {
id: string;
};

@ -1,8 +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 { setRoleRoutes } from "@/routes/role.routes";
import { setTariffRoutes } from "@/routes/tariff.routes";
import type { FastifyInstance } from "fastify"; export const combineRoutes = (router: Router): void => {
router.group("/role", setRoleRoutes);
export const combineRoutes = (server: FastifyInstance): void => { router.group("/account", setAccountRoutes);
server.register(setRoleRoutes, { prefix: "/role" }); router.group("/privilege", setPrivilegeRoutes);
router.group("/tariff", setTariffRoutes);
}; };

@ -1,20 +1,7 @@
import path from "path"; import path from "path";
import dotenv from "dotenv"; import dotenv from "dotenv";
import { DEFAULT } from "@/constants/default"; import { defineEnvironment } from "./define-environment";
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;
};
export const configureENV = () => { export const configureENV = () => {
const environment = defineEnvironment(); const environment = defineEnvironment();

@ -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;
};

@ -1,9 +1,13 @@
import cors from "@fastify/cors"; import cors from "@fastify/cors";
import cookie from "@fastify/cookie"; import cookie from "@fastify/cookie";
import jwt from "@fastify/jwt"; import jwt from "@fastify/jwt";
import swagger from "@fastify/swagger";
import swaggerUI from "@fastify/swagger-ui";
import printRoutes from "@/plugins/print-routes"; import printRoutes from "@/plugins/print-routes";
import { DEFAULT } from "@/constants/default";
import type { FastifyInstance } from "fastify"; import type { FastifyInstance } from "fastify";
import type { PluginsOptions } from "@/types/configuration/plugins-options"; import type { PluginsOptions } from "@/types/configuration/plugins-options";
@ -11,6 +15,8 @@ export const registerFastifyPlugins = (fastify: FastifyInstance, options: Plugin
fastify.register(cors, options.cors); fastify.register(cors, options.cors);
fastify.register(cookie, options.cookie); fastify.register(cookie, options.cookie);
fastify.register(jwt, options.jwt); fastify.register(jwt, options.jwt);
fastify.register(swagger, DEFAULT.swaggerOptions);
fastify.register(swaggerUI, DEFAULT.swaggerUIOptions);
fastify.register(printRoutes); fastify.register(printRoutes);
return fastify; return fastify;

@ -11,10 +11,11 @@ export const CONFIGURATION = {
database: process.env.DB_NAME || "database", database: process.env.DB_NAME || "database",
}, },
service: { service: {
privateAccessSecretKey: process.env.PRIVATE_ACCESS_SECRET_KEY || "",
publicAccessSecretKey: process.env.PUBLIC_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, salt: Number(process.env.SALT) || 10,
}, },
authService: {
host: process.env.AUTH_SERVICE_HOST || "",
port: Number(process.env.AUTH_SERVICE_PORT) || 8081,
},
} as const; } as const;

@ -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 = { type Default = {
readonly [key: string]: string; readonly fastifyOptions: FastifyServerOptions;
readonly environment: Environment; readonly swaggerOptions: FastifyDynamicSwaggerOptions;
readonly swaggerUIOptions: FastifySwaggerUiOptions;
}; };
export const DEFAULT: Default = { 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 <token>",
type: "http",
bearerFormat: "JWT",
scheme: "bearer",
},
},
},
},
hideUntagged: true,
},
swaggerUIOptions: {
routePrefix: "/swagger",
uiConfig: {
docExpansion: "full",
deepLinking: false,
},
},
}; };

@ -0,0 +1,137 @@
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, 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)) {
reply.status(400);
return new Error("invalid user id");
}
const account = await AccountModel.findOne({ userId: 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 [getAccountRequestParams, error] = validateEmptyFields(request.params || {}, ["userId"]);
if (error) {
reply.status(400);
return error;
}
if (!Types.ObjectId.isValid(getAccountRequestParams.userId)) {
reply.status(400);
return new Error("invalid user id");
}
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;
};
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;
};

@ -0,0 +1,14 @@
import type { FastifyRequest } from "fastify";
export type GetAccountRequest = FastifyRequest<{
Params?: {
userId?: string;
};
}>;
export type SetAccountRoleRequest = FastifyRequest<{
Body: {
userId?: string;
role?: string;
};
}>;

@ -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 }>();
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();
};

@ -0,0 +1,28 @@
import type { Privilege } from "@/types/models/privilege.type";
import type { RawPrivilege } from "./types";
export const validatePrivilege = (privilege: RawPrivilege): Error | null => {
const typeValues: typeof privilege.type[] = ["count", "day", "full"];
if (!typeValues.includes(privilege.type)) {
return new Error("invalid <type> value");
}
if (isNaN(Number(privilege.price))) {
return new Error("price must be a number");
}
return null;
};
export const convertPrivilegiesToMap = (privilegies: Privilege[]) => {
return privilegies.reduce<Record<string, Privilege[]>>((accamulator, privilege) => {
if (!accamulator[privilege.serviceKey]) {
accamulator[privilege.serviceKey] = [];
}
accamulator[privilege.serviceKey].push(privilege);
return accamulator;
}, {});
};

@ -0,0 +1,329 @@
import { Types } from "mongoose";
import { PrivilegeModel } from "@/models/privilege.model";
import { validateEmptyFields } from "@/utils/validate-empty-fields";
import { convertPrivilegiesToMap, validatePrivilege } from "./helpers";
import type { FastifyReply } from "fastify";
import type {
GetServicePrivilegiesRequest,
RegisterPrivilegeRequest,
GetPrivilegeRequest,
RegisterPrivilegiesRequest,
RemovePrivilegeRequest,
} from "./types";
export const registerPrivilegies = async (request: RegisterPrivilegiesRequest, reply: FastifyReply) => {
const [requestBody, errorEmpty] = validateEmptyFields(request.body || {}, ["privilegies"]);
if (errorEmpty) {
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 || {},
["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 () => PrivilegeModel.find({ isDeleted: false }).lean();
export const getAllPrivilegiesMap = async () => {
const privilegies = await PrivilegeModel.find({ isDeleted: false }).lean();
if (!privilegies.length) {
return {};
}
return convertPrivilegiesToMap(privilegies);
};
export const getServicePrivilegies = async (request: GetServicePrivilegiesRequest, reply: FastifyReply) => {
const [requestParams, error] = validateEmptyFields(request.params || {}, ["serviceKey"]);
if (error) {
reply.status(400);
return error;
}
return PrivilegeModel.find({ serviceKey: requestParams.serviceKey, isDeleted: false }).lean();
};
export const getPrivilege = async (request: GetPrivilegeRequest, reply: FastifyReply) => {
const [requestParams, error] = validateEmptyFields(request.params || {}, ["privilegeId"]);
if (error) {
reply.status(400);
return error;
}
if (!Types.ObjectId.isValid(requestParams.privilegeId)) {
reply.status(400);
return new Error("invalid id");
}
const privilege = await PrivilegeModel.findOne({ privilegeId: requestParams.privilegeId, isDeleted: false }).lean();
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,
updatedAt: new Date(),
});
return Promise.all(replacePrivilegeRequests);
};
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(privilegeId)) {
reply.status(400);
return new Error("invalid id");
}
const privilege = await PrivilegeModel.findOneAndUpdate(
{ privilegeId },
{ $set: { isDeleted: true, deletedAt: new Date() } }
);
if (!privilege) {
reply.status(404);
return new Error("privilege not found");
}
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"]);
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 },
{
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: 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: true, deletedAt: new Date() } }
);
if (!privilege) {
reply.status(404);
return new Error("privilege not found");
}
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;
};

@ -0,0 +1,39 @@
import type { FastifyRequest } from "fastify";
export type RawPrivilege = {
name?: string;
privilegeId?: string;
serviceKey?: string;
description?: string;
type?: string;
value?: string;
price?: number;
};
export type RegisterPrivilegeRequest = FastifyRequest<{
Body?: RawPrivilege;
}>;
export type RegisterPrivilegiesRequest = FastifyRequest<{
Body?: {
privilegies?: RawPrivilege[];
};
}>;
export type GetServicePrivilegiesRequest = FastifyRequest<{
Params?: {
serviceKey?: string;
};
}>;
export type GetPrivilegeRequest = FastifyRequest<{
Params?: {
privilegeId?: string;
};
}>;
export type RemovePrivilegeRequest = FastifyRequest<{
Body?: {
privilegeId?: string;
};
}>;

@ -73,33 +73,32 @@ export const removeRole = async (request: RemoveRoleRequest, reply: FastifyReply
}; };
export const getRole = async (request: GetRoleRequest, reply: FastifyReply) => { export const getRole = async (request: GetRoleRequest, reply: FastifyReply) => {
const { name, id } = request.body || {};
const { query } = request.params || {}; 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) { if (!query) {
reply.status(400); reply.status(400);
return new Error("query is empty"); return new Error("query is empty");
} }
if (Types.ObjectId.isValid(query)) { 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) => { export const replaceRole = async (request: UpdateRoleRequest, reply: FastifyReply) => {

@ -9,9 +9,6 @@ export type CreateRoleRequest = FastifyRequest<{ Body: RoleRequest }>;
export type RemoveRoleRequest = FastifyRequest<{ Body: { id?: string } }>; export type RemoveRoleRequest = FastifyRequest<{ Body: { id?: string } }>;
export type GetRoleRequest = FastifyRequest<{ export type GetRoleRequest = FastifyRequest<{
Body?: Pick<RoleRequest, "name"> & {
id?: string;
};
Params: { Params: {
query?: string; query?: string;
}; };

@ -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<TariffMessage>, Error | null] => {
const [validatedTariff, errorEmpty] = validateEmptyFields(
tariff || {},
["isCustom", "name", "price", "privilegieIDArray"],
false
);
if (errorEmpty) {
return [validatedTariff, errorEmpty];
}
if (isNaN(Number(validatedTariff.price))) {
return [validatedTariff, new Error("invalid 'price' value")];
}
return [validatedTariff, null];
};

@ -0,0 +1,200 @@
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, RemoveTariffRequest } 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 privilegeId of requestBody.privilegieIDArray) {
if (!Types.ObjectId.isValid(privilegeId)) {
reply.status(404);
return new Error(`privilege id <${privilegeId}> invalid`);
}
}
const privilegies = await PrivilegeModel.find({ privilegeId: requestBody.privilegieIDArray }).lean();
const privilegiesMap = requestBody.privilegieIDArray.reduce<Record<string, Privilege>>((accamulator, privilegeId) => {
const findedPrivilege = privilegies.find((privilege) => privilege.privilegeId === privilegeId);
if (!findedPrivilege) {
return accamulator;
}
accamulator[privilegeId] = findedPrivilege;
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 privilegeId of requestBody.privilegieIDArray) {
if (!Types.ObjectId.isValid(privilegeId)) {
reply.status(404);
return new Error(`privilege id <${privilegeId}> invalid`);
}
}
const privilegies = await PrivilegeModel.find({ privilegeId: requestBody.privilegieIDArray }).lean();
const privilegiesMap = requestBody.privilegieIDArray.reduce<Record<string, Privilege>>((accamulator, privilegeId) => {
const findedPrivilege = privilegies.find((privilege) => privilege.privilegeId === privilegeId);
if (!findedPrivilege) {
return accamulator;
}
accamulator[privilegeId] = findedPrivilege;
return accamulator;
}, {});
await tariff.replaceOne({
name: requestBody.name,
price: requestBody.price,
isCustom: requestBody.isCustom,
privilegies: privilegiesMap,
});
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;
};

@ -0,0 +1,23 @@
import type { FastifyRequest } from "fastify";
import type { TariffMessage } from "@/types/messages/tariff-message.type";
export type GetTariffRequest = FastifyRequest<{
Params?: {
id?: string;
};
}>;
export type RemoveTariffRequest = FastifyRequest<{
Body?: {
id?: string;
};
}>;
export type CreateTariffRequest = FastifyRequest<{
Body?: TariffMessage;
}>;
export type ReplaceTariffRequest = FastifyRequest<{
Body?: TariffMessage;
Params?: { id?: string };
}>;

@ -15,7 +15,11 @@ const server = new Server({
jwt: { jwt: {
secret: { secret: {
public: CONFIGURATION.service.publicAccessSecretKey, public: CONFIGURATION.service.publicAccessSecretKey,
private: CONFIGURATION.service.privateAccessSecretKey, private: "secret",
},
verify: {
allowedIss: "pena-auth-service",
allowedAud: "pena",
}, },
}, },
}, },

@ -0,0 +1,35 @@
import { Schema, model, SchemaDefinition } from "mongoose";
import { eloquentSchema } from "./eloquent.schema";
import type { Account } from "@/types/models/account.type";
const schema: SchemaDefinition<Account> = {
userId: {
type: String,
required: true,
},
nickname: {
type: String,
required: true,
default: "nickname",
},
avatar: {
type: String,
default: "/media/img/no-avatar.png",
},
role: {
type: String,
default: "user",
},
...eloquentSchema,
};
const schemaSettings = {
versionKey: false,
collection: "accounts",
};
const AccountSchema = new Schema<Account>(schema, schemaSettings);
export const AccountModel = model("Account", AccountSchema);

@ -1,7 +1,8 @@
import type { SchemaDefinition } from "mongoose"; import { SchemaDefinition } from "mongoose";
import type { EloquentModel } from "@/types/models/eloquent-model";
export const eloquentModelSchema: SchemaDefinition<EloquentModel> = { import type { Eloquent } from "@/types/models/eloquent.type";
export const eloquentSchema: SchemaDefinition<Eloquent> = {
createdAt: { createdAt: {
type: Date, type: Date,
required: true, required: true,

@ -0,0 +1,50 @@
import { Schema, model, SchemaDefinition } from "mongoose";
import { eloquentSchema } from "./eloquent.schema";
import type { Privilege } from "@/types/models/privilege.type";
const schema: SchemaDefinition<Privilege> = {
name: {
type: String,
required: true,
},
privilegeId: {
type: String,
required: true,
index: true,
},
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,
},
...eloquentSchema,
};
const schemaSettings = {
versionKey: false,
collection: "privilegies",
};
export const PrivilegeSchema = new Schema<Privilege>(schema, schemaSettings);
export const PrivilegeModel = model("Privilege", PrivilegeSchema);

@ -1,6 +1,6 @@
import { Schema, model, SchemaDefinition } from "mongoose"; import { Schema, model, SchemaDefinition } from "mongoose";
import { eloquentModelSchema } from "./eloquent-model.schema"; import { eloquentSchema } from "./eloquent.schema";
import type { Role } from "@/types/models/role.type"; import type { Role } from "@/types/models/role.type";
@ -14,7 +14,7 @@ const schema: SchemaDefinition<Role> = {
of: Boolean, of: Boolean,
default: {}, default: {},
}, },
...eloquentModelSchema, ...eloquentSchema,
}; };
const schemaSettings = { const schemaSettings = {

@ -0,0 +1,36 @@
import { Schema, model, SchemaDefinition } from "mongoose";
import { eloquentSchema } from "./eloquent.schema";
import { PrivilegeSchema } from "./privilege.model";
import type { Tariff } from "@/types/models/tariff.type";
const schema: SchemaDefinition<Tariff> = {
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
isCustom: {
type: Boolean,
default: false,
},
privilegies: {
type: Map,
of: PrivilegeSchema,
default: {},
},
...eloquentSchema,
};
const schemaSettings = {
versionKey: false,
collection: "tariffs",
};
const TariffSchema = new Schema<Tariff>(schema, schemaSettings);
export const TariffModel = model("Tariff", TariffSchema);

@ -1,23 +0,0 @@
import { Schema, model, SchemaDefinition } from "mongoose";
import type { Token } from "@/types/models/token.type";
const schema: SchemaDefinition<Token> = {
userId: {
type: String,
required: true,
},
refreshToken: {
type: String,
required: true,
},
};
const schemaSettings = {
versionKey: false,
collection: "tokens",
};
const TokenSchema = new Schema<Token>(schema, schemaSettings);
export const TokenModel = model("Token", TokenSchema);

@ -1,42 +0,0 @@
import { Schema, model, SchemaDefinition } from "mongoose";
import { eloquentModelSchema } from "./eloquent-model.schema";
import type { User } from "@/types/models/user.type";
const schema: SchemaDefinition<User> = {
login: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
phoneNumber: {
type: String,
required: true,
},
avatar: {
type: String,
default: "/media/img/no-avatar.png",
},
role: {
type: String,
default: "user",
},
...eloquentModelSchema,
};
const schemaSettings = {
versionKey: false,
collection: "users",
};
const UserSchema = new Schema<User>(schema, schemaSettings);
export const UserModel = model("User", UserSchema);

@ -8,7 +8,7 @@ type PrintRoutesRouteOptions = {
const METHODS_ORDER = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS"]; const METHODS_ORDER = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS"];
const printRoutes = (routes: Array<RouteOptions & PrintRoutesRouteOptions> = []) => { const printRoutes = (routes: Array<RouteOptions & PrintRoutesRouteOptions> = [], isSwagger = false) => {
if (routes.length === 0) { if (routes.length === 0) {
return; return;
} }
@ -21,8 +21,9 @@ const printRoutes = (routes: Array<RouteOptions & PrintRoutesRouteOptions> = [])
return accamulator + `${methodsValue}\t${route.url}\n`; 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( export default fastifyPlugin(
@ -34,7 +35,7 @@ export default fastifyPlugin(
}); });
instance.addHook("onReady", (next) => { instance.addHook("onReady", (next) => {
printRoutes(routes); printRoutes(routes, !!instance.swagger);
next(); next();
}); });

@ -0,0 +1,32 @@
import { Router } from "@/server/router";
import {
createAccount,
getAccount,
setAccountRole,
getAllAccounts,
deleteAccount,
removeAccount,
restoreAccount,
} from "@/handlers/account";
import { verifyUser } from "@/handlers/auth/middleware";
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("/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 });
};

@ -0,0 +1,40 @@
import { Router } from "@/server/router";
import {
registerPrivilege,
getAllPrivilegies,
getAllPrivilegiesMap,
getPrivilege,
getServicePrivilegies,
replacePrivilege,
registerPrivilegies,
removePrivilege,
replacePrivilegies,
restorePrivilege,
} from "@/handlers/privilege";
import {
getPrivilegiesSchema,
getPrivilegiesMapSchema,
getPrivilegeSchema,
getServicePrivilegiesSchema,
registerPrivilegeSchema,
registerPrivilegiesSchema,
replacePrivilegeSchema,
replacePrivilegiesSchema,
removePrivilegeSchema,
restorePrivilegeSchema,
} from "@/swagger/privilege";
export const setPrivilegeRoutes = (router: Router): void => {
router.get("/", getAllPrivilegies, { schema: getPrivilegiesSchema });
router.get("/service", getAllPrivilegiesMap, { schema: getPrivilegiesMapSchema });
router.get("/:privilegeId", getPrivilege, { schema: getPrivilegeSchema });
router.get("/service/:serviceKey", getServicePrivilegies, { schema: getServicePrivilegiesSchema });
router.post("/", registerPrivilege, { schema: registerPrivilegeSchema });
router.post("/many", registerPrivilegies, { schema: registerPrivilegiesSchema });
router.post("/restore/", restorePrivilege, { schema: restorePrivilegeSchema });
router.put("/", replacePrivilege, { schema: replacePrivilegeSchema });
router.put("/many", replacePrivilegies, { schema: replacePrivilegiesSchema });
router.delete("/", removePrivilege, { schema: removePrivilegeSchema });
};

@ -1,3 +1,5 @@
import { Router } from "@/server/router";
import { import {
getRole, getRole,
getAllRoles, getAllRoles,
@ -9,17 +11,24 @@ import {
restoreRole, restoreRole,
} from "@/handlers/roles"; } from "@/handlers/roles";
import type { FastifyInstance, FastifyPluginOptions } from "fastify"; import {
getRolesSchema,
getRoleSchema,
createRoleSchema,
restoreRoleSchema,
updateRoleSchema,
replaceRoleSchema,
removeRoleSchema,
deleteRoleSchema,
} from "@/swagger/role";
export const setRoleRoutes = <T = FastifyPluginOptions>(server: FastifyInstance, opts: T, done: () => void): void => { export const setRoleRoutes = (router: Router): void => {
server.get("/all", getAllRoles); router.get("/", getAllRoles, { schema: getRolesSchema });
server.get("/:query", getRole); router.get("/:query", getRole, { schema: getRoleSchema });
server.post("/", createRole); router.post("/", createRole, { schema: createRoleSchema });
server.post("/restore", restoreRole); router.post("/restore", restoreRole, { schema: restoreRoleSchema });
server.patch("/:query", updateRole); router.patch("/:query", updateRole, { schema: updateRoleSchema });
server.put("/:query", replaceRole); router.put("/:query", replaceRole, { schema: replaceRoleSchema });
server.delete("/", removeRole); router.delete("/", removeRole, { schema: removeRoleSchema });
server.delete("/delete", deleteRole); router.delete("/delete", deleteRole, { schema: deleteRoleSchema });
done();
}; };

@ -0,0 +1,32 @@
import { Router } from "@/server/router";
import {
createTariff,
replaceTariff,
getTariff,
getTariffs,
removeTariff,
restoreTariff,
deleteTariff,
} from "@/handlers/tariff";
import { verifyUser } from "@/handlers/auth/middleware";
import {
getTariffSchema,
getTariffsSchema,
createTariffsSchema,
replaceTariffsSchema,
removeTariffsSchema,
restoreTariffsSchema,
deleteTariffsSchema,
} from "@/swagger/tariff";
export const setTariffRoutes = (router: Router): void => {
router.get("/", getTariffs, { 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 });
};

@ -1,10 +1,14 @@
import fastify, { FastifyInstance } from "fastify"; import fastify, { FastifyInstance } from "fastify";
import { connect as mongoConnect } from "mongoose"; import { connect as mongoConnect } from "mongoose";
import { Router } from "./router";
import { registerFastifyPlugins } from "@/configuration/register-fastify-plugins"; import { registerFastifyPlugins } from "@/configuration/register-fastify-plugins";
import { combineRoutes } from "@/configuration/combine-routes"; import { combineRoutes } from "@/configuration/combine-routes";
import { constituteMongoURI } from "@/configuration/constitute-mongo-uri"; import { constituteMongoURI } from "@/configuration/constitute-mongo-uri";
import { DEFAULT } from "@/constants/default";
import type { PluginsOptions } from "@/types/configuration/plugins-options"; import type { PluginsOptions } from "@/types/configuration/plugins-options";
import type { DatabaseOptions } from "@/types/configuration/database-options"; import type { DatabaseOptions } from "@/types/configuration/database-options";
@ -34,16 +38,13 @@ export class Server {
constructor({ serverOptions, databaseOptions, pluginsOptions }: ServerArgs) { constructor({ serverOptions, databaseOptions, pluginsOptions }: ServerArgs) {
this.serverOptions = serverOptions; this.serverOptions = serverOptions;
this.databaseOptions = databaseOptions; this.databaseOptions = databaseOptions;
this.fastify = fastify({ this.fastify = fastify(DEFAULT.fastifyOptions);
logger: false,
bodyLimit: 30 * 1024 * 1024,
});
if (pluginsOptions) { if (pluginsOptions) {
registerFastifyPlugins(this.fastify, pluginsOptions); registerFastifyPlugins(this.fastify, pluginsOptions);
} }
combineRoutes(this.fastify); combineRoutes(new Router(this.fastify));
} }
public start = async () => { public start = async () => {
@ -51,9 +52,10 @@ export class Server {
const databaseConnection = this.databaseOptions ? mongoConnect(constituteMongoURI(this.databaseOptions)) : null; const databaseConnection = this.databaseOptions ? mongoConnect(constituteMongoURI(this.databaseOptions)) : null;
await Promise.all([databaseConnection, fasticyConnection]) 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)); .catch((reason) => console.error(reason));
}; };
public getFastifyInstance = () => this.fastify;
} }

77
src/server/router.ts Normal file

@ -0,0 +1,77 @@
import type {
FastifyInstance,
RouteHandlerMethod,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
RouteShorthandOptions,
FastifyPluginOptions,
RawServerDefault,
RouteGenericInterface,
} from "fastify";
type HandlerMethod<T extends RouteGenericInterface> = RouteHandlerMethod<
RawServerDefault,
RawRequestDefaultExpression<RawServerDefault>,
RawReplyDefaultExpression<RawServerDefault>,
T
>;
export class Router {
private fastifyInstance: FastifyInstance;
constructor(fastifyInstance: FastifyInstance) {
this.fastifyInstance = fastifyInstance;
}
public group = (path: string, combineRoutes: (router: Router) => void) => {
this.fastifyInstance.register(
(server: FastifyInstance, opts: FastifyPluginOptions, done: () => void) => {
const router = new Router(server);
combineRoutes(router);
done();
},
{ prefix: path }
);
};
public get = <T extends RouteGenericInterface>(
path: string,
handler: HandlerMethod<T>,
options: RouteShorthandOptions = {}
) => {
this.fastifyInstance.get(path, options, handler);
};
public post = <T extends RouteGenericInterface>(
path: string,
handler: HandlerMethod<T>,
options: RouteShorthandOptions = {}
) => {
this.fastifyInstance.post(path, options, handler);
};
public put = <T extends RouteGenericInterface>(
path: string,
handler: HandlerMethod<T>,
options: RouteShorthandOptions = {}
) => {
this.fastifyInstance.put(path, options, handler);
};
public patch = <T extends RouteGenericInterface>(
path: string,
handler: HandlerMethod<T>,
options: RouteShorthandOptions = {}
) => {
this.fastifyInstance.patch(path, options, handler);
};
public delete = <T extends RouteGenericInterface>(
path: string,
handler: HandlerMethod<T>,
options: RouteShorthandOptions = {}
) => {
this.fastifyInstance.delete(path, options, handler);
};
}

@ -1,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"
}
}
}
}
}
}

@ -0,0 +1,64 @@
import { getAccountParams, setAccountRoleBody } from "./inputs";
import {
getAccountResponse,
createAccountResponse,
setAccountRoleResponse,
getAccountsResponse,
removeRoleResponse,
} 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",
tags: ["account"],
params: getAccountParams,
response: getAccountResponse,
};
export const createAccountSchema: SwaggerSchema = {
summary: "Создание аккаунта",
tags: ["account"],
response: createAccountResponse,
security: [{ bearer: [] }],
};
export const setAccountRoleSchema: SwaggerSchema = {
summary: "Присвоение роли пользователя",
tags: ["account"],
body: setAccountRoleBody,
response: setAccountRoleResponse,
security: [{ bearer: [] }],
};
export const removeAccountSchema: SwaggerSchema = {
summary: "Удаление аккаунта",
description: "Помечает аккаунт удалённым, но не удаляет его из БД",
tags: ["account"],
response: 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: [] }],
};

@ -0,0 +1,27 @@
import type { SwaggerMessage } from "@/types/swagger.type";
export const getAccountParams: SwaggerMessage = {
type: "object",
required: ["userId"],
properties: {
userId: {
type: "string",
description: "ID пользователя",
},
},
};
export const setAccountRoleBody: SwaggerMessage = {
type: "object",
required: ["userId", "role"],
properties: {
userId: {
type: "string",
description: "ID пользователя",
},
role: {
type: "string",
description: "название роли",
},
},
};

@ -0,0 +1,49 @@
import type { SwaggerMessage } from "@/types/swagger.type";
export const account: SwaggerMessage = {
description: "Аккаунт",
type: "object",
properties: {
_id: { type: "string" },
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: [
{
_id: "807f1f77bcf81cd799439011",
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",
},
{
_id: "807f1f77bcf81cd799439011",
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",
},
],
};

@ -0,0 +1,40 @@
import { swaggerError } from "@/utils/swagger-error";
import { account } from "./models";
import type { SwaggerMessage } from "@/types/swagger.type";
export const getAccountsResponse: Record<string, SwaggerMessage> = {
200: {
type: "array",
description: "Массив аккаунтов",
items: account,
},
};
export const getAccountResponse: Record<string, SwaggerMessage> = {
200: account,
400: swaggerError(400, "invalid user id"),
};
export const createAccountResponse: Record<string, SwaggerMessage> = {
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"),
};
export const setAccountRoleResponse: Record<string, SwaggerMessage> = {
200: account,
400: swaggerError(400, "invalid user id"),
401: swaggerError(401, "invalid token"),
404: swaggerError(404, "user not found"),
};
export const removeRoleResponse: Record<string, SwaggerMessage> = {
200: account,
400: swaggerError(400, "invalid user id"),
401: swaggerError(401, "invalid token"),
404: swaggerError(404, "user not found"),
};

@ -0,0 +1,84 @@
import { privilegeBody, registerPrivilegiesBody, getPrivilegeParams, getServicePrivilegiesParams } from "./inputs";
import {
getPrivilegeReponse,
getPrivilegiesReponse,
getAllPrivilegiesMapReponse,
getAllPrivilegiesReponse,
registerPrivilegeResponse,
replacePrivilegeResponse,
registerPrivilegiesResponse,
replacePrivilegiesResponse,
removePrivilegeResponse,
} from "./responses";
import type { SwaggerSchema } from "@/types/swagger.type";
export const getPrivilegiesSchema: SwaggerSchema = {
summary: "Получение всех привелегий",
description: "Получение всех привелегий в виде массива",
tags: ["privilege"],
response: getAllPrivilegiesReponse,
};
export const getPrivilegiesMapSchema: SwaggerSchema = {
summary: "Получение всех привелегий",
description: "Получение всех привелегий в виде объекта ключ-значение, где ключём является serviceKey",
tags: ["privilege"],
response: getAllPrivilegiesMapReponse,
};
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 registerPrivilegiesSchema: SwaggerSchema = {
summary: "Регистрация привелегий сервиса",
tags: ["privilege"],
body: registerPrivilegiesBody,
response: registerPrivilegiesResponse,
};
export const replacePrivilegeSchema: SwaggerSchema = {
summary: "Замена привилегии сервиса",
tags: ["privilege"],
body: privilegeBody,
response: replacePrivilegeResponse,
};
export const replacePrivilegiesSchema: SwaggerSchema = {
summary: "Замена привилегий сервиса",
tags: ["privilege"],
body: registerPrivilegiesBody,
response: replacePrivilegiesResponse,
};
export const removePrivilegeSchema: SwaggerSchema = {
summary: "Удаление привелегии",
tags: ["privilege"],
body: getPrivilegeParams,
response: removePrivilegeResponse,
};
export const restorePrivilegeSchema: SwaggerSchema = {
summary: "Восстановление привилегии",
tags: ["privilege"],
body: getPrivilegeParams,
response: removePrivilegeResponse,
};

@ -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: "Количество попыток использования",
privilegeId: "507f1f77bcf86cd799439011",
serviceKey: "docx-templater-service",
description: "Количество попыток использования",
type: "count",
value: "200",
price: 12300,
},
],
};
export const privilegiesBody: SwaggerMessage = {
type: "array",
items: privilegeBody,
};
export const registerPrivilegiesBody: SwaggerMessage = {
type: "object",
properties: {
privilegies: privilegiesBody,
},
};
export const getPrivilegeParams: SwaggerMessage = {
type: "object",
required: ["privilegeId"],
properties: {
privilegeId: {
type: "string",
description: "ID привилегии (privilegeId)",
},
},
examples: [{ privilegeId: "507f1f77bcf86cd799439011" }],
};
export const getServicePrivilegiesParams: SwaggerMessage = {
type: "object",
required: ["serviceKey"],
properties: {
serviceKey: {
type: "string",
description: "Ключ сервиса",
},
},
examples: [{ serviceKey: "docx-templater-service" }],
};

@ -0,0 +1,46 @@
import type { SwaggerMessage, SwaggerValueType } from "@/types/swagger.type";
const privilegeExamples: SwaggerValueType[] = [
{
_id: "207f1f67bcf86cd799439011",
name: "Количество попыток использования",
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: {
_id: { type: "string" },
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,
};

@ -0,0 +1,95 @@
import { swaggerError } from "@/utils/swagger-error";
import { privilege } from "./models";
import type { SwaggerMessage } from "@/types/swagger.type";
export const getAllPrivilegiesReponse: Record<string, SwaggerMessage> = {
200: {
type: "array",
description: "Привилегии",
items: privilege,
},
};
export const getAllPrivilegiesMapReponse: Record<string, SwaggerMessage> = {
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<string, SwaggerMessage> = {
200: {
type: "array",
description: "Массив привилегий",
items: privilege,
},
};
export const getPrivilegeReponse: Record<string, SwaggerMessage> = {
200: privilege,
400: swaggerError(400, "invalid id"),
404: swaggerError(404, "privilege not found"),
};
export const registerPrivilegeResponse: Record<string, SwaggerMessage> = {
200: privilege,
400: swaggerError(400, "price must be a number"),
409: swaggerError(409, "privilege already exist"),
};
export const registerPrivilegiesResponse: Record<string, SwaggerMessage> = {
200: {
type: "array",
description: "Массив привилегий",
items: privilege,
},
400: swaggerError(400, "price must be a number"),
};
export const replacePrivilegeResponse: Record<string, SwaggerMessage> = {
200: privilege,
400: swaggerError(400, "invalid 'type' value"),
404: swaggerError(404, "privilege not found"),
};
export const replacePrivilegiesResponse: Record<string, SwaggerMessage> = {
200: {
type: "array",
description: "Массив привилегий",
items: privilege,
},
400: swaggerError(400, "invalid 'type' value"),
};
export const removePrivilegeResponse: Record<string, SwaggerMessage> = {
200: privilege,
400: swaggerError(400, "invalid id"),
404: swaggerError(404, "privilege not found"),
};

71
src/swagger/role/index.ts Normal file

@ -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,
};

@ -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" }],
};

@ -0,0 +1,32 @@
import type { SwaggerMessage } from "@/types/swagger.type";
export const role: SwaggerMessage = {
type: "object",
description: "Роль",
properties: {
_id: { type: "string" },
name: { type: "string" },
permissions: {
type: "object",
additionalProperties: { type: "boolean" },
},
},
examples: [
{
_id: "638388e120c70c17eb123d37",
name: "user",
permissions: {
read: true,
write: false,
},
},
{
_id: "638388f720c70c17eb123d3a",
name: "admin",
permissions: {
read: true,
write: true,
},
},
],
};

@ -0,0 +1,51 @@
import { swaggerError } from "@/utils/swagger-error";
import { role } from "./models";
import type { SwaggerMessage } from "@/types/swagger.type";
export const getRolesReponse: Record<string, SwaggerMessage> = {
200: {
type: "array",
description: "Массив ролей",
items: role,
},
};
export const getRoleReponse: Record<string, SwaggerMessage> = {
200: role,
400: swaggerError(400, "query is empty"),
404: swaggerError(404, "role not found"),
};
export const createRoleReponse: Record<string, SwaggerMessage> = {
200: role,
400: swaggerError(400, "field <name> is empty"),
409: swaggerError(409, "role already exist"),
};
export const restoreRoleReponse: Record<string, SwaggerMessage> = {
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<string, SwaggerMessage> = {
200: role,
400: swaggerError(400, "either name or permissions must be filled"),
404: swaggerError(404, "role not found"),
};
export const removeRoleReponse: Record<string, SwaggerMessage> = {
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<string, SwaggerMessage> = {
200: role,
400: swaggerError(400, "wrong id"),
404: swaggerError(404, "role not found"),
};

@ -0,0 +1,66 @@
import { getTariffParams, tariffBody, replaceTariffParams } from "./inputs";
import {
getTariffReponse,
getTariffsReponse,
createTariffReponse,
replaceTariffReponse,
removeTariffReponse,
} 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,
security: [{ bearer: [] }],
response: createTariffReponse,
};
export const replaceTariffsSchema: SwaggerSchema = {
summary: "Замена тарифа",
tags: ["tariff"],
params: replaceTariffParams,
body: tariffBody,
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,
};

@ -0,0 +1,48 @@
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", "privilegieIDArray"],
properties: {
name: { type: "string" },
price: { type: "number" },
isCustom: { type: "boolean" },
privilegieIDArray: {
type: "array",
items: { type: "string" },
},
},
examples: [
{
name: "Использование сервисов",
price: 14000,
isCustom: false,
privilegieIDArray: ["507f1f77bcf86cd799439011"],
},
],
};
export const replaceTariffParams: SwaggerMessage = {
type: "object",
required: ["id"],
properties: {
id: {
type: "string",
description: "ID тарифа",
},
},
examples: [{ id: "63a7d47ba24613f98562bafa" }],
};

@ -0,0 +1,97 @@
import type { SwaggerMessage } from "@/types/swagger.type";
const privilege: SwaggerMessage = {
type: "object",
description: "Привилегия",
properties: {
_id: { type: "string" },
name: { type: "string" },
privilegeId: { type: "string" },
serviceKey: { type: "string" },
description: { type: "string" },
type: { type: "string" },
value: { type: "string" },
price: { type: "number" },
},
examples: [
{
_id: "507f1f77bcf86cd799439011",
name: "Количество попыток использования",
privilegeId: "507f1f77bcf86cd799439011",
serviceKey: "docx-templater-service",
description: "Количество попыток использования сервиса генерации шаблонов",
type: "count",
value: "200",
price: 12300,
},
],
};
export const tariff: SwaggerMessage = {
type: "object",
description: "Тариф",
properties: {
_id: { type: "string" },
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: "Использование сервисов",
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",
},
],
};

@ -0,0 +1,40 @@
import { swaggerError } from "@/utils/swagger-error";
import { tariff } from "./models";
import type { SwaggerMessage } from "@/types/swagger.type";
export const getTariffReponse: Record<string, SwaggerMessage> = {
200: tariff,
400: swaggerError(400, "invalid id"),
404: swaggerError(404, "tariff not found"),
};
export const getTariffsReponse: Record<string, SwaggerMessage> = {
200: {
type: "array",
description: "Массив тарифов",
items: tariff,
},
};
export const createTariffReponse: Record<string, SwaggerMessage> = {
200: tariff,
400: swaggerError(400, "invalid 'price' value"),
401: swaggerError(400, "invalid user id"),
404: swaggerError(404, "privilege with id <privilegeId> not found"),
};
export const replaceTariffReponse: Record<string, SwaggerMessage> = {
200: tariff,
400: swaggerError(400, "invalid id"),
401: swaggerError(400, "invalid user id"),
404: swaggerError(404, "tariff not found"),
};
export const removeTariffReponse: Record<string, SwaggerMessage> = {
200: tariff,
400: swaggerError(400, "invalid id"),
401: swaggerError(400, "invalid user id"),
404: swaggerError(404, "tariff not found"),
};

8
src/types/fastify-jwt.d.ts vendored Normal file

@ -0,0 +1,8 @@
import "@fastify/jwt";
declare module "@fastify/jwt" {
interface FastifyJWT {
payload: { id: string };
user: { id: string };
}
}

@ -0,0 +1,9 @@
import type { Eloquent } from "../models/eloquent.type";
import type { Tariff } from "../models/tariff.type";
import type { ObjectWithPossibleFields } from "../object-with-possible-fields";
export type TariffMessage = ObjectWithPossibleFields<
Omit<Tariff, keyof Eloquent | "privilegies"> & {
privilegieIDArray: string[];
}
>;

@ -0,0 +1,8 @@
import type { Eloquent } from "./eloquent.type";
export type Account = Eloquent & {
userId: string;
nickname: string;
avatar: string;
role: string;
};

@ -1,4 +1,4 @@
export type EloquentModel = { export type Eloquent = {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
deletedAt: Date; deletedAt: Date;

@ -0,0 +1,11 @@
import type { Eloquent } from "./eloquent.type";
export type Privilege = Eloquent & {
name: string;
privilegeId: string;
serviceKey: string;
description: string;
type: "count" | "day" | "full";
value: string;
price: number;
};

@ -1,6 +1,6 @@
import type { EloquentModel } from "./eloquent-model"; import type { Eloquent } from "./eloquent.type";
export type Role = EloquentModel & { export type Role = Eloquent & {
name: string; name: string;
permissions: Record<string, boolean>; permissions: Record<string, boolean>;
}; };

@ -0,0 +1,9 @@
import type { Privilege } from "./privilege.type";
import type { Eloquent } from "./eloquent.type";
export type Tariff = Eloquent & {
name: string;
price: number;
isCustom: boolean;
privilegies: Record<string, Omit<Privilege, keyof Eloquent>>;
};

@ -0,0 +1,8 @@
import type { Eloquent } from "./eloquent.type";
export type User = Eloquent & {
_id: string;
login: string;
email: string;
phoneNumber: string;
};

@ -0,0 +1,3 @@
export type ObjectWithPossibleFields<KeyValue extends Record<string, unknown>> = {
[Key in keyof KeyValue]?: KeyValue[Key];
};

50
src/types/swagger.type.ts Normal file

@ -0,0 +1,50 @@
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 | boolean;
properties?: Record<string, SwaggerMessage | Record<string, SwaggerValueType>>;
examples?: SwaggerValueType[];
example?: SwaggerValueType;
required?: string[];
oneOf?: SwaggerMessage[];
allOf?: SwaggerMessage[];
};
export type SwaggerSchema = FastifySchema & {
params?: SwaggerMessage;
body?: SwaggerMessage;
querystring?: SwaggerMessage;
response?: Record<number, SwaggerMessage>;
};

@ -0,0 +1,20 @@
import type { SwaggerMessage } from "@/types/swagger.type";
const STATUS_CODE_MAP: Record<number, string> = {
400: "Bad Request",
401: "Unauthorized",
404: "Not Found",
409: "Conflict",
500: "Internal Server Error",
};
export const swaggerError = (code: number, message: string): SwaggerMessage => ({
type: "object",
description: STATUS_CODE_MAP[code],
properties: {
statusCode: { type: "integer" },
error: { type: "string" },
message: { type: "string" },
},
examples: [{ statusCode: code, error: STATUS_CODE_MAP[code], message: message }],
});

@ -2,14 +2,23 @@ import type { ObjectWithRequiredFields } from "@/types/object-with-required-fiel
type KeyValue = Record<string, unknown>; type KeyValue = Record<string, unknown>;
type Keys<T> = Array<keyof T>; type Keys<T> = Array<keyof T>;
type ValidateEmptyFields<T extends KeyValue> = [ObjectWithRequiredFields<T> | undefined, Error | null]; type ValidateEmptyFields<T extends KeyValue> = [ObjectWithRequiredFields<T>, Error | null];
export const validateEmptyFields = <T extends KeyValue>(
record: T,
keys: Keys<T> = [],
strict = true
): ValidateEmptyFields<T> => {
const validatedRecord = record as ObjectWithRequiredFields<T>;
export const validateEmptyFields = <T extends KeyValue>(record: T, keys: Keys<T> = []): ValidateEmptyFields<T> => {
for (const key of keys) { for (const key of keys) {
if (!record[key]) { if (strict && !record[key]) {
return [undefined, new Error(`field <${key as string}> is empty`)]; 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<T>, null]; return [validatedRecord, null];
}; };

265
yarn.lock

@ -1204,6 +1204,11 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" 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": "@fastify/ajv-compiler@^3.3.1":
version "3.4.0" version "3.4.0"
resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz#e001b7e234b5b704654b1d617d69fa63c348f2a7" resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz#e001b7e234b5b704654b1d617d69fa63c348f2a7"
@ -1257,6 +1262,41 @@
fastify-plugin "^4.0.0" fastify-plugin "^4.0.0"
steed "^1.1.3" 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": "@humanwhocodes/config-array@^0.11.6":
version "0.11.7" version "0.11.7"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f"
@ -2049,6 +2089,11 @@ async-mutex@^0.3.2:
dependencies: dependencies:
tslib "^2.3.1" 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: atomic-sleep@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
@ -2063,6 +2108,15 @@ avvio@^8.2.0:
debug "^4.0.0" debug "^4.0.0"
fastq "^1.6.1" 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: babel-jest@^29.3.1:
version "29.3.1" version "29.3.1"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44"
@ -2170,6 +2224,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" 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: braces@^3.0.2, braces@~3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@ -2366,6 +2427,13 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 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: commander@^9.0.0:
version "9.4.1" version "9.4.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"
@ -2389,6 +2457,13 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 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: conventional-changelog-angular@^5.0.11:
version "5.0.13" version "5.0.13"
resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c"
@ -2468,6 +2543,13 @@ dargs@^7.0.0:
resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc"
integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== 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: 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" version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@ -2475,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: dependencies:
ms "2.1.2" 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: debug@^3.2.7:
version "3.2.7" version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@ -2530,11 +2605,26 @@ define-properties@^1.1.3, define-properties@^1.1.4:
has-property-descriptors "^1.0.0" has-property-descriptors "^1.0.0"
object-keys "^1.1.1" 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: denque@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== 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: detect-newline@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@ -2590,6 +2680,11 @@ ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
dependencies: dependencies:
safe-buffer "^5.0.1" 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: electron-to-chromium@^1.4.251:
version "1.4.284" version "1.4.284"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
@ -2605,6 +2700,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 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: end-of-stream@^1.4.1:
version "1.4.4" version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@ -2678,6 +2778,11 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 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: escape-string-regexp@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@ -2873,6 +2978,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 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: event-target-shim@^5.0.0:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
@ -3134,11 +3244,30 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== 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: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 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: fs-constants@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
@ -3267,6 +3396,17 @@ glob@^7.1.3, glob@^7.1.4:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.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: global-dirs@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
@ -3392,6 +3532,17 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== 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: https-proxy-agent@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
@ -3459,7 +3610,7 @@ inflight@^1.0.4:
once "^1.3.0" once "^1.3.0"
wrappy "1" 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" version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -4091,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" 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== 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: json-schema-traverse@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@ -4357,6 +4517,23 @@ micromatch@^4.0.4:
braces "^3.0.2" braces "^3.0.2"
picomatch "^2.3.1" 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"
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: mimic-fn@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@ -4379,6 +4556,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" 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: minimist-options@4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
@ -4617,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" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4"
integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== 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: once@^1.3.0, once@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -4640,6 +4831,11 @@ open@^8.4.0:
is-docker "^2.1.1" is-docker "^2.1.1"
is-wsl "^2.2.0" 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: optionator@^0.9.1:
version "0.9.1" version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@ -4843,6 +5039,11 @@ proxy-addr@^2.0.7:
forwarded "0.2.0" forwarded "0.2.0"
ipaddr.js "1.9.1" 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: pstree.remy@^1.1.8:
version "1.1.8" version "1.1.8"
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
@ -4878,6 +5079,11 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== 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: react-is@^18.0.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
@ -5013,7 +5219,7 @@ reusify@^1.0.0, reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 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" version "1.3.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
@ -5032,7 +5238,7 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" 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" version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -5104,11 +5310,35 @@ semver@~7.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== 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: set-cookie-parser@^2.4.1:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b"
integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== 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: shebang-command@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@ -5261,6 +5491,11 @@ stack-utils@^2.0.3:
dependencies: dependencies:
escape-string-regexp "^2.0.0" 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: steed@^1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/steed/-/steed-1.1.3.tgz#f1525dd5adb12eb21bf74749537668d625b9abc5" resolved "https://registry.yarnpkg.com/steed/-/steed-1.1.3.tgz#f1525dd5adb12eb21bf74749537668d625b9abc5"
@ -5489,6 +5724,11 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" 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: touch@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
@ -5790,6 +6030,11 @@ yaml@^1.10.0:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== 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: yargs-parser@^20.2.3:
version "20.2.9" version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"