diff --git a/src/configuration/combine-routes.ts b/src/configuration/combine-routes.ts index 9936e35..ae683ad 100644 --- a/src/configuration/combine-routes.ts +++ b/src/configuration/combine-routes.ts @@ -1,6 +1,7 @@ import { setAccountRoutes } from "@/routes/account.routes"; import { setPrivilegeRoutes } from "@/routes/privilege.routes"; import { setRoleRoutes } from "@/routes/role.routes"; +import { setTariffRoutes } from "@/routes/tariff.routes"; import type { FastifyInstance } from "fastify"; @@ -8,4 +9,5 @@ export const combineRoutes = (server: FastifyInstance): void => { server.register(setRoleRoutes, { prefix: "/role" }); server.register(setAccountRoutes, { prefix: "/account" }); server.register(setPrivilegeRoutes, { prefix: "/privilege" }); + server.register(setTariffRoutes, { prefix: "/tariff" }); }; diff --git a/src/handlers/account/middleware.ts b/src/handlers/auth/middleware.ts similarity index 100% rename from src/handlers/account/middleware.ts rename to src/handlers/auth/middleware.ts diff --git a/src/handlers/tariff/helpers.ts b/src/handlers/tariff/helpers.ts new file mode 100644 index 0000000..b5ae578 --- /dev/null +++ b/src/handlers/tariff/helpers.ts @@ -0,0 +1,22 @@ +import { validateEmptyFields } from "@/utils/validate-empty-fields"; + +import type { TariffMessage } from "@/types/messages/tariff-message.type"; +import type { ObjectWithRequiredFields } from "@/types/object-with-required-fields"; + +export const validateTariff = (tariff: TariffMessage): [ObjectWithRequiredFields, Error | null] => { + const [validatedTariff, errorEmpty] = validateEmptyFields( + tariff, + ["isCustom", "name", "price", "privilegies"], + false + ); + + if (errorEmpty) { + return [validatedTariff, errorEmpty]; + } + + if (isNaN(Number(validatedTariff.price))) { + return [validatedTariff, new Error("invalid 'price' value")]; + } + + return [validatedTariff, null]; +}; diff --git a/src/handlers/tariff/index.ts b/src/handlers/tariff/index.ts new file mode 100644 index 0000000..14ce648 --- /dev/null +++ b/src/handlers/tariff/index.ts @@ -0,0 +1,125 @@ +import { Types } from "mongoose"; + +import { TariffModel } from "@/models/tariff.model"; +import { PrivilegeModel } from "@/models/privilege.model"; + +import { validateEmptyFields } from "@/utils/validate-empty-fields"; +import { validateTariff } from "./helpers"; + +import type { FastifyReply } from "fastify"; + +import type { Privilege } from "@/types/models/privilege.type"; +import type { CreateTariffRequest, GetTariffRequest, ReplaceTariffRequest } from "./types"; + +export const getTariffs = async () => TariffModel.find({}).lean(); + +export const getTariff = async (request: GetTariffRequest, reply: FastifyReply) => { + const [requestParams, error] = validateEmptyFields(request.params || {}, ["id"]); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(requestParams.id)) { + reply.status(400); + return new Error("invalid id"); + } + + const tariff = await TariffModel.findById(requestParams.id).lean(); + + if (!tariff) { + reply.status(404); + return new Error("tariff not found"); + } + + return tariff; +}; + +export const createTariff = async (request: CreateTariffRequest, reply: FastifyReply) => { + const [requestBody, error] = validateTariff(request.body || {}); + + if (error) { + reply.status(400); + return error; + } + + for (const privilege of requestBody.privilegies) { + const findedPrivilege = await PrivilegeModel.findOne({ privilegeId: privilege.privilegeId }).lean(); + + if (!findedPrivilege) { + reply.status(404); + return new Error(`privilege with id ${privilege.privilegeId} not found`); + } + } + + const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { + if (!accamulator[privilege.privilegeId]) { + accamulator[privilege.privilegeId] = []; + } + + accamulator[privilege.privilegeId].push(privilege); + + return accamulator; + }, {}); + + const newTariff = new TariffModel({ + name: requestBody.name, + price: requestBody.price, + isCustom: requestBody.isCustom, + privilegies: privilegiesMap, + }); + + await newTariff.save(); + + return newTariff; +}; + +export const replaceTariff = async (request: ReplaceTariffRequest, reply: FastifyReply) => { + const [requestBody, error] = validateTariff(request.body || {}); + + if (error) { + reply.status(400); + return error; + } + + if (!Types.ObjectId.isValid(request.params?.id || "")) { + reply.status(400); + return new Error("invalid id"); + } + + const tariff = await TariffModel.findById(request.params?.id); + + if (!tariff) { + reply.status(404); + return new Error("tariff not found"); + } + + for (const privilege of requestBody.privilegies) { + const findedPrivilege = await PrivilegeModel.findOne({ privilegeId: privilege.privilegeId }).lean(); + + if (!findedPrivilege) { + reply.status(404); + return new Error(`privilege with id ${privilege.privilegeId} not found`); + } + } + + const privilegiesMap = requestBody.privilegies.reduce>((accamulator, privilege) => { + if (!accamulator[privilege.privilegeId]) { + accamulator[privilege.privilegeId] = []; + } + + accamulator[privilege.privilegeId].push(privilege); + + return accamulator; + }, {}); + + await tariff.replaceOne({ + name: requestBody.name, + price: requestBody.price, + isCustom: requestBody.isCustom, + privilegies: privilegiesMap, + }); + + return tariff; +}; diff --git a/src/handlers/tariff/types.ts b/src/handlers/tariff/types.ts new file mode 100644 index 0000000..2eab2ed --- /dev/null +++ b/src/handlers/tariff/types.ts @@ -0,0 +1,11 @@ +import type { FastifyRequest } from "fastify"; +import type { CreateTariffRoute, ReplaceTariffRoute } from "@/types/routes/tariff-routes.type"; + +export type GetTariffRequest = FastifyRequest<{ + Params?: { + id?: string; + }; +}>; + +export type CreateTariffRequest = FastifyRequest; +export type ReplaceTariffRequest = FastifyRequest; diff --git a/src/models/account.model.ts b/src/models/account.model.ts index 00f217f..7c2820f 100644 --- a/src/models/account.model.ts +++ b/src/models/account.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { eloquentModelSchema } from "./eloquent-model.schema"; +import { EloquentSchema } from "./eloquent-model.schema"; import type { Account } from "@/types/models/account.type"; @@ -22,7 +22,7 @@ const schema: SchemaDefinition = { type: String, default: "user", }, - ...eloquentModelSchema, + ...EloquentSchema, }; const schemaSettings = { diff --git a/src/models/eloquent-model.schema.ts b/src/models/eloquent-model.schema.ts index be46084..832bd37 100644 --- a/src/models/eloquent-model.schema.ts +++ b/src/models/eloquent-model.schema.ts @@ -1,7 +1,8 @@ -import type { SchemaDefinition } from "mongoose"; +import { Schema, SchemaDefinition } from "mongoose"; + import type { EloquentModel } from "@/types/models/eloquent-model.type"; -export const eloquentModelSchema: SchemaDefinition = { +export const schema: SchemaDefinition = { createdAt: { type: Date, required: true, @@ -22,3 +23,10 @@ export const eloquentModelSchema: SchemaDefinition = { default: false, }, }; + +const schemaSettings = { + versionKey: false, + collection: "privilegies", +}; + +export const EloquentSchema = new Schema(schema, schemaSettings); diff --git a/src/models/privilege.model.ts b/src/models/privilege.model.ts index c5226f4..fb7180a 100644 --- a/src/models/privilege.model.ts +++ b/src/models/privilege.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { eloquentModelSchema } from "./eloquent-model.schema"; +import { EloquentSchema } from "./eloquent-model.schema"; import type { Privilege } from "@/types/models/privilege.type"; @@ -37,7 +37,7 @@ const schema: SchemaDefinition = { type: Number, required: true, }, - ...eloquentModelSchema, + ...EloquentSchema, }; const schemaSettings = { @@ -45,6 +45,6 @@ const schemaSettings = { collection: "privilegies", }; -const PrivilegeSchema = new Schema(schema, schemaSettings); +export const PrivilegeSchema = new Schema(schema, schemaSettings); export const PrivilegeModel = model("Privilege", PrivilegeSchema); diff --git a/src/models/role.model.ts b/src/models/role.model.ts index fb060d3..188ddb5 100644 --- a/src/models/role.model.ts +++ b/src/models/role.model.ts @@ -1,6 +1,6 @@ import { Schema, model, SchemaDefinition } from "mongoose"; -import { eloquentModelSchema } from "./eloquent-model.schema"; +import { EloquentSchema } from "./eloquent-model.schema"; import type { Role } from "@/types/models/role.type"; @@ -14,7 +14,7 @@ const schema: SchemaDefinition = { of: Boolean, default: {}, }, - ...eloquentModelSchema, + ...EloquentSchema, }; const schemaSettings = { diff --git a/src/models/tariff.model.ts b/src/models/tariff.model.ts new file mode 100644 index 0000000..219fe04 --- /dev/null +++ b/src/models/tariff.model.ts @@ -0,0 +1,36 @@ +import { Schema, model, SchemaDefinition } from "mongoose"; + +import { EloquentSchema } from "./eloquent-model.schema"; +import { PrivilegeSchema } from "./privilege.model"; + +import type { Tariff } from "@/types/models/tariff.type"; + +const schema: SchemaDefinition = { + name: { + type: String, + required: true, + }, + price: { + type: Number, + required: true, + }, + isCustom: { + type: Boolean, + default: false, + }, + privilegies: { + type: Map, + of: PrivilegeSchema, + default: {}, + }, + ...EloquentSchema, +}; + +const schemaSettings = { + versionKey: false, + collection: "tariffs", +}; + +const TariffSchema = new Schema(schema, schemaSettings); + +export const TariffModel = model("Tariff", TariffSchema); diff --git a/src/routes/account.routes.ts b/src/routes/account.routes.ts index 38c93fe..4d5852d 100644 --- a/src/routes/account.routes.ts +++ b/src/routes/account.routes.ts @@ -1,5 +1,5 @@ import { createAccount, getAccount } from "@/handlers/account"; -import { verifyUser } from "@/handlers/account/middleware"; +import { verifyUser } from "@/handlers/auth/middleware"; import type { FastifyInstance, FastifyPluginOptions } from "fastify"; import type { GetAccountRoute, CreateAccountRoute } from "@/types/routes/account-routes.type"; diff --git a/src/routes/tariff.routes.ts b/src/routes/tariff.routes.ts new file mode 100644 index 0000000..85ea1fb --- /dev/null +++ b/src/routes/tariff.routes.ts @@ -0,0 +1,14 @@ +import { createTariff, replaceTariff, getTariff, getTariffs } from "@/handlers/tariff"; +import { verifyUser } from "@/handlers/auth/middleware"; + +import type { FastifyInstance, FastifyPluginOptions } from "fastify"; +import type { CreateTariffRoute, ReplaceTariffRoute } from "@/types/routes/tariff-routes.type"; + +export const setTariffRoutes = (server: FastifyInstance, opts: T, done: () => void): void => { + server.get("/:id", getTariff); + server.get("/", getTariffs); + server.post("/", { preHandler: [verifyUser] }, createTariff); + server.put("/", { preHandler: [verifyUser] }, replaceTariff); + + done(); +}; diff --git a/src/swagger.json b/src/swagger.json deleted file mode 100644 index 46ed610..0000000 --- a/src/swagger.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "PenaHub Server", - "version": "1.0.11", - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - } - }, - "externalDocs": { - "description": "Find out more about Swagger", - "url": "http://swagger.io" - }, - "servers": [ - { - "url": "http://84.201.155.77:8080" - } - ], - "paths": { - "/": { - "get": { - "tags": [ - "/" - ], - "summary": "Список пользователей", - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/auth": { - "get": { - "tags": [ - "auth" - ], - "summary": "Страница регистрации", - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/api/v1/auth": { - "post": { - "tags": [ - "auth" - ], - "summary": "Регистрация нового пользователя", - "requestBody": { - "content": { - "application/urlencoded": { - "schema": { - "type": "object", - "properties": { - "login": { - "type": "string", - "example": "valera24" - }, - "email": { - "type": "string", - "example": "valera24@mail.com" - }, - "phoneNumber": { - "type": "string", - "example": 79993337733 - }, - "password": { - "type": "string", - "example": "secret" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/login": { - "get": { - "tags": [ - "login" - ], - "summary": "Страница авторизации", - "parameters": [ - { - "name": "return", - "in": "query", - "description": "URL на который будет перенаправлен пользователь после авторизации", - "required": false - } - ], - "responses": { - "200": { - "description": "Successful" - } - } - } - }, - "/api/v1/login": { - "post": { - "tags": [ - "login" - ], - "summary": "Авторизация пользователя", - "requestBody": { - "content": { - "application/urlencoded": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "valera24@mail.com" - }, - "password": { - "type": "string", - "example": "secret" - }, - "goto": { - "type": "string", - "example": "http://penahub.quiz/signedup" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful (Если не передан goto)" - }, - "304": { - "description": "Successful (Если передан goto)" - }, - "401": { - "description": "password incorrect" - }, - "404": { - "description": "User not found" - } - } - } - } - } - } \ No newline at end of file diff --git a/src/types/messages/tariff-message.type.ts b/src/types/messages/tariff-message.type.ts new file mode 100644 index 0000000..e420035 --- /dev/null +++ b/src/types/messages/tariff-message.type.ts @@ -0,0 +1,10 @@ +import type { EloquentModel } from "../models/eloquent-model.type"; +import type { Privilege } from "../models/privilege.type"; +import type { Tariff } from "../models/tariff.type"; +import type { ObjectWithPossibleFields } from "../object-with-possible-fields"; + +export type TariffMessage = ObjectWithPossibleFields< + Omit & { + privilegies: Privilege[]; + } +>; diff --git a/src/types/models/tariff.type.ts b/src/types/models/tariff.type.ts new file mode 100644 index 0000000..67120e0 --- /dev/null +++ b/src/types/models/tariff.type.ts @@ -0,0 +1,9 @@ +import type { Privilege } from "./privilege.type"; +import type { EloquentModel } from "./eloquent-model.type"; + +export type Tariff = EloquentModel & { + name: string; + price: number; + isCustom: boolean; + privilegies: Record; +}; diff --git a/src/types/object-with-possible-fields.ts b/src/types/object-with-possible-fields.ts new file mode 100644 index 0000000..45b616b --- /dev/null +++ b/src/types/object-with-possible-fields.ts @@ -0,0 +1,3 @@ +export type ObjectWithPossibleFields> = { + [Key in keyof KeyValue]?: KeyValue[Key]; +}; diff --git a/src/types/routes/tariff-routes.type.ts b/src/types/routes/tariff-routes.type.ts new file mode 100644 index 0000000..042e14b --- /dev/null +++ b/src/types/routes/tariff-routes.type.ts @@ -0,0 +1,11 @@ +import type { RequestGenericInterface } from "fastify"; +import type { TariffMessage } from "../messages/tariff-message.type"; + +export type CreateTariffRoute = RequestGenericInterface & { + Body?: TariffMessage; +}; + +export type ReplaceTariffRoute = RequestGenericInterface & { + Body?: TariffMessage; + Params?: { id?: string }; +};