feat: permissions & pagination
This commit is contained in:
parent
8ff5d8aba8
commit
3b1e2fb174
@ -12,12 +12,3 @@ DB_PORT=27017
|
||||
DB_USERNAME=test
|
||||
DB_PASSWORD=test
|
||||
DB_NAME=admin
|
||||
|
||||
# Other
|
||||
PUBLIC_ACCESS_SECRET_KEY= |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLW1tlHyKC9AG0hGpmkksET2DE
|
||||
r7ojSPemxFWAgFgcPJWQ7x3uNbsdJ3bIZFoA/FClaWKMCZmjnH9tv0bKZtY/CDhM
|
||||
ZEyHpMruRSn6IKrxjtQZWy4uv/w6MzUeyBYG0OvNCiYpdvz5SkAGAUHD5ZNFqn2w
|
||||
KKFD0I2Dr59BFVSGJwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
@ -2,37 +2,38 @@ version: "3.3"
|
||||
|
||||
services:
|
||||
admin:
|
||||
container_name: hub-admin-backend-service
|
||||
tty: true
|
||||
build:
|
||||
context: ../../.
|
||||
dockerfile: Dockerfile
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- DB_HOST=10.6.0.11
|
||||
- DB_HOST=admin-mongo
|
||||
- DB_PORT=27017
|
||||
- ENVIRONMENT=staging
|
||||
- HTTP_HOST=0.0.0.0
|
||||
- HTTP_PORT=8005
|
||||
- AUTH_SERVICE_HOST=http://pena-auth-service
|
||||
- AUTH_SERVICE_HOST=http://auth
|
||||
- AUTH_SERVICE_PORT=8000
|
||||
- DB_USERNAME=test
|
||||
- DB_PASSWORD=test
|
||||
- DB_NAME=admin
|
||||
- PUBLIC_ACCESS_SECRET_KEY= |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLW1tlHyKC9AG0hGpmkksET2DE
|
||||
r7ojSPemxFWAgFgcPJWQ7x3uNbsdJ3bIZFoA/FClaWKMCZmjnH9tv0bKZtY/CDhM
|
||||
ZEyHpMruRSn6IKrxjtQZWy4uv/w6MzUeyBYG0OvNCiYpdvz5SkAGAUHD5ZNFqn2w
|
||||
KKFD0I2Dr59BFVSGJwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
networks:
|
||||
- dev
|
||||
depends_on:
|
||||
- admin-mongo
|
||||
ports:
|
||||
- 8005:8005
|
||||
|
||||
mongo:
|
||||
admin-mongo:
|
||||
image: "mongo:6.0.3"
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: test
|
||||
MONGO_INITDB_ROOT_PASSWORD: test
|
||||
ports:
|
||||
- "27017:27017"
|
||||
- 27017:27017
|
||||
networks:
|
||||
- dev
|
||||
|
||||
|
11
package.json
11
package.json
@ -13,8 +13,8 @@
|
||||
"test": "jest --coverage",
|
||||
"test:watch": "jest --watch",
|
||||
"build": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||
"compose:start:dev": "docker-compose -f deployments/dev/docker-compose.yaml up -d",
|
||||
"compose:stop:dev": "docker-compose -f deployments/dev/docker-compose.yaml down --volumes --rmi local",
|
||||
"compose:dev:start": "docker-compose -f deployments/dev/docker-compose.yaml up -d",
|
||||
"compose:dev:stop": "docker-compose -f deployments/dev/docker-compose.yaml down --volumes --rmi local",
|
||||
"code:check": "prettier --check \"src/**/*.{ts,tsx,js,css,scss,html}\"",
|
||||
"code:format": "prettier --write \"src/**/*.{ts,tsx,js,css,scss,html}\"",
|
||||
"code:format:specific-file": "prettier --write",
|
||||
@ -25,6 +25,8 @@
|
||||
"@fastify/cookie": "^8.3.0",
|
||||
"@fastify/cors": "^8.2.0",
|
||||
"@fastify/jwt": "^6.3.3",
|
||||
"@fastify/swagger": "^8.2.1",
|
||||
"@fastify/swagger-ui": "^1.3.0",
|
||||
"axios": "^1.2.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^16.0.3",
|
||||
@ -33,11 +35,9 @@
|
||||
"fastify-print-routes": "^2.0.6",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"@fastify/swagger": "^8.2.1",
|
||||
"@fastify/swagger-ui": "^1.3.0",
|
||||
"typescript-transform-paths": "^3.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -57,6 +57,7 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"husky": "^7.0.4",
|
||||
"jest": "^29.3.1",
|
||||
"jest-mock-extended": "^3.0.4",
|
||||
"nodemon": "^2.0.20",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
|
@ -4,10 +4,12 @@ import { setAccountRoutes } from "@/routes/account.routes";
|
||||
import { setPrivilegeRoutes } from "@/routes/privilege.routes";
|
||||
import { setRoleRoutes } from "@/routes/role.routes";
|
||||
import { setTariffRoutes } from "@/routes/tariff.routes";
|
||||
import { setPermissionRoutes } from "@/routes/permission.routes";
|
||||
|
||||
export const combineRoutes = (router: Router): void => {
|
||||
router.group("/role", setRoleRoutes);
|
||||
router.group("/account", setAccountRoutes);
|
||||
router.group("/privilege", setPrivilegeRoutes);
|
||||
router.group("/tariff", setTariffRoutes);
|
||||
router.group("/permission", setPermissionRoutes);
|
||||
};
|
||||
|
15
src/constants/permissions.ts
Normal file
15
src/constants/permissions.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { Permission } from "@/types/models/permission.type";
|
||||
|
||||
const DELETE_ACCOUNT_PERMISSION: Permission = {
|
||||
name: "hub-admin-backend-service.deleteAccount",
|
||||
description: "Разрешение на удаление чужого аккаунта пользователем",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
isDeleted: false,
|
||||
};
|
||||
|
||||
export const PERMISSIONS = {
|
||||
deleteAccount: DELETE_ACCOUNT_PERMISSION.name,
|
||||
} as const;
|
||||
|
||||
export const PERMISSION_LIST: Array<Permission> = [DELETE_ACCOUNT_PERMISSION];
|
45
src/handlers/account/helpers.test.ts
Normal file
45
src/handlers/account/helpers.test.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { determinePaginationParameters } from "./helpers";
|
||||
|
||||
import type { PaginationParams } from "./types";
|
||||
|
||||
describe("determinePaginationParameters", () => {
|
||||
const testCases: Array<{
|
||||
input: PaginationParams;
|
||||
result: ReturnType<typeof determinePaginationParameters>;
|
||||
}> = [
|
||||
{
|
||||
input: {
|
||||
page: 1,
|
||||
limit: 2,
|
||||
},
|
||||
result: {
|
||||
page: 1,
|
||||
limit: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
page: -100,
|
||||
limit: 200,
|
||||
},
|
||||
result: {
|
||||
page: 1,
|
||||
limit: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
page: 6,
|
||||
limit: -100,
|
||||
},
|
||||
result: {
|
||||
page: 6,
|
||||
limit: 100,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)("Успешное определение значений пагинации %j", ({ input, result }) => {
|
||||
expect(determinePaginationParameters(input)).toEqual(result);
|
||||
});
|
||||
});
|
18
src/handlers/account/helpers.ts
Normal file
18
src/handlers/account/helpers.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { ObjectWithRequiredFields } from "@/types/object-with-required-fields";
|
||||
import type { PaginationParams } from "./types";
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
const DEFAULT_LIMIT = 100;
|
||||
|
||||
export const determinePaginationParameters = ({
|
||||
page: optionalPage,
|
||||
limit: optionalLimit,
|
||||
}: PaginationParams): ObjectWithRequiredFields<PaginationParams> => {
|
||||
const page = !optionalPage || optionalPage < 1 ? DEFAULT_PAGE : optionalPage;
|
||||
const limit = !optionalLimit || optionalLimit < 1 ? DEFAULT_LIMIT : optionalLimit;
|
||||
|
||||
return {
|
||||
page,
|
||||
limit: limit > DEFAULT_LIMIT ? DEFAULT_LIMIT : limit,
|
||||
};
|
||||
};
|
@ -2,14 +2,28 @@ import { Types } from "mongoose";
|
||||
|
||||
import { AccountModel } from "@/models/account.model";
|
||||
import { RoleModel } from "@/models/role.model";
|
||||
import { PermissionModule } from "@/services/permission/permission.module";
|
||||
|
||||
import { getUser } from "@/clients/auth";
|
||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
||||
import { determinePaginationParameters } from "./helpers";
|
||||
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import type { GetAccountRequest, SetAccountRoleRequest } from "./types";
|
||||
import type { Account } from "@/types/models/account.type";
|
||||
import type { GetAccountRequest, SetAccountRoleRequest, GetAccountsRequest, GetAccountsResponse } from "./types";
|
||||
|
||||
export const getAllAccounts = async () => AccountModel.find({}).lean();
|
||||
export const getAccounts = async (request: GetAccountsRequest): Promise<GetAccountsResponse> => {
|
||||
const { page, limit } = determinePaginationParameters(request?.query || {});
|
||||
|
||||
const accountsCount = await AccountModel.countDocuments();
|
||||
|
||||
const totalPages = Math.ceil(accountsCount / limit);
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const accounts = await AccountModel.find({}).sort({ createdAt: "desc" }).skip(offset).limit(limit).lean();
|
||||
|
||||
return { accounts, totalPages };
|
||||
};
|
||||
|
||||
export const createAccount = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
||||
@ -38,17 +52,17 @@ export const createAccount = async (request: FastifyRequest, reply: FastifyReply
|
||||
return createdAccount.save();
|
||||
};
|
||||
|
||||
export const getAccountByID = async (request: GetAccountRequest, reply: FastifyReply) => {
|
||||
export const getAccountByID = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
||||
const [getAccountRequestParams, error] = validateEmptyFields(request.params || {}, ["userId"]);
|
||||
|
||||
if (error) {
|
||||
reply.status(400);
|
||||
return error;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(getAccountRequestParams.userId)) {
|
||||
reply.status(400);
|
||||
return new Error("invalid user id");
|
||||
throw new Error("invalid user id");
|
||||
}
|
||||
|
||||
const account = await AccountModel.findOne({ userId: getAccountRequestParams.userId }).lean();
|
||||
@ -61,10 +75,10 @@ export const getAccountByID = async (request: GetAccountRequest, reply: FastifyR
|
||||
return account;
|
||||
};
|
||||
|
||||
export const getAccount = async (request: GetAccountRequest, reply: FastifyReply) => {
|
||||
export const getAccount = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
||||
reply.status(400);
|
||||
return new Error("invalid user id");
|
||||
throw new Error("invalid user id");
|
||||
}
|
||||
|
||||
const account = await AccountModel.findOne({ userId: request.user.id }).lean();
|
||||
@ -105,10 +119,10 @@ export const setAccountRole = async (request: SetAccountRoleRequest, reply: Fast
|
||||
return account;
|
||||
};
|
||||
|
||||
export const removeAccount = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
export const removeAccount = async (request: FastifyRequest, reply: FastifyReply): Promise<Account> => {
|
||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
||||
reply.status(400);
|
||||
return new Error("invalid user id");
|
||||
throw new Error("invalid user id");
|
||||
}
|
||||
|
||||
const account = await AccountModel.findOneAndUpdate(
|
||||
@ -118,32 +132,92 @@ export const removeAccount = async (request: FastifyRequest, reply: FastifyReply
|
||||
|
||||
if (!account) {
|
||||
reply.status(404);
|
||||
return new Error("account not found");
|
||||
throw new Error("account not found");
|
||||
}
|
||||
|
||||
return account;
|
||||
};
|
||||
|
||||
export const deleteAccount = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
export const removeAccountById = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
||||
const [{ userId }, error] = validateEmptyFields(request.params || {}, ["userId"]);
|
||||
|
||||
if (error) {
|
||||
reply.status(400);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(userId)) {
|
||||
reply.status(400);
|
||||
throw new Error("invalid user id");
|
||||
}
|
||||
|
||||
const account = await AccountModel.findOne({ userId: request.user.id });
|
||||
|
||||
if (!account) {
|
||||
reply.status(404);
|
||||
throw new Error("account not found");
|
||||
}
|
||||
|
||||
const isAvailableToDelete = await PermissionModule.deleteAccount(account);
|
||||
|
||||
if (!isAvailableToDelete) {
|
||||
throw new Error("the user doesn't have the permission to delete account");
|
||||
}
|
||||
|
||||
await account.updateOne({ $set: { isDeleted: true, deletedAt: new Date() } });
|
||||
|
||||
return account;
|
||||
};
|
||||
|
||||
export const deleteAccount = async (request: FastifyRequest, reply: FastifyReply): Promise<Account> => {
|
||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
||||
reply.status(400);
|
||||
return new Error("invalid user id");
|
||||
throw 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");
|
||||
throw new Error("account not found");
|
||||
}
|
||||
|
||||
return account;
|
||||
};
|
||||
|
||||
export const restoreAccount = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
export const deleteAccountById = async (request: GetAccountRequest, reply: FastifyReply): Promise<Account> => {
|
||||
const [{ userId }, error] = validateEmptyFields(request.params || {}, ["userId"]);
|
||||
|
||||
if (error) {
|
||||
reply.status(400);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(userId)) {
|
||||
reply.status(400);
|
||||
throw new Error("invalid user id");
|
||||
}
|
||||
|
||||
const account = await AccountModel.findByIdAndDelete({ userId: request.user.id }).lean();
|
||||
|
||||
if (!account) {
|
||||
reply.status(404);
|
||||
throw new Error("account not found");
|
||||
}
|
||||
|
||||
const isAvailableToDelete = await PermissionModule.deleteAccount(account);
|
||||
|
||||
if (!isAvailableToDelete) {
|
||||
throw new Error("the user doesn't have the permission to delete account");
|
||||
}
|
||||
|
||||
return account;
|
||||
};
|
||||
|
||||
export const restoreAccount = async (request: FastifyRequest, reply: FastifyReply): Promise<Account> => {
|
||||
if (!Types.ObjectId.isValid(request.user.id)) {
|
||||
reply.status(400);
|
||||
return new Error("invalid user id");
|
||||
throw new Error("invalid user id");
|
||||
}
|
||||
|
||||
const account = await AccountModel.findOneAndUpdate(
|
||||
@ -153,7 +227,7 @@ export const restoreAccount = async (request: FastifyRequest, reply: FastifyRepl
|
||||
|
||||
if (!account) {
|
||||
reply.status(404);
|
||||
return new Error("account not found");
|
||||
throw new Error("account not found");
|
||||
}
|
||||
|
||||
return account;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { FastifyRequest } from "fastify";
|
||||
import type { Account } from "@/types/models/account.type";
|
||||
|
||||
export type GetAccountRequest = FastifyRequest<{
|
||||
Params?: {
|
||||
@ -12,3 +13,17 @@ export type SetAccountRoleRequest = FastifyRequest<{
|
||||
role?: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type GetAccountsRequest = FastifyRequest<{
|
||||
Querystring?: PaginationParams;
|
||||
}>;
|
||||
|
||||
export type GetAccountsResponse = {
|
||||
accounts: Account[];
|
||||
totalPages: number;
|
||||
};
|
||||
|
||||
export type PaginationParams = {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction as Done } from "fastify";
|
||||
|
||||
export const verifyUser = async (request: FastifyRequest, reply: FastifyReply, done: Done) => {
|
||||
console.info("---------------------------verifyUser------------------------------");
|
||||
|
||||
try {
|
||||
const { id } = await request.jwtVerify<{ id?: string }>();
|
||||
|
||||
@ -12,6 +14,7 @@ export const verifyUser = async (request: FastifyRequest, reply: FastifyReply, d
|
||||
request.user = { id };
|
||||
} catch (nativeError) {
|
||||
reply.status(401);
|
||||
console.info("---------------------------verifyUser error------------------------------", nativeError);
|
||||
return reply.send(nativeError);
|
||||
}
|
||||
|
||||
|
166
src/handlers/permission/index.ts
Normal file
166
src/handlers/permission/index.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import { Types } from "mongoose";
|
||||
|
||||
import { PermissionModule } from "@/services/permission/permission.module";
|
||||
import { PermissionModel } from "@/models/permission.model";
|
||||
|
||||
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
||||
|
||||
import type { FastifyReply } from "fastify";
|
||||
import type { Permission } from "@/types/models/permission.type";
|
||||
import type { CreatePermissionRequest, GetPermissionByIdRequest, UpdatePermissionRequest } from "./types";
|
||||
|
||||
export const getAllPermissions = async (): Promise<Permission[]> => PermissionModule.getAllPermissions();
|
||||
|
||||
export const getPermissionById = async (
|
||||
request: GetPermissionByIdRequest,
|
||||
reply: FastifyReply
|
||||
): Promise<Permission | undefined> => {
|
||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params || {}, ["permissionId"]);
|
||||
|
||||
if (validateParamsError) {
|
||||
reply.status(400);
|
||||
throw validateParamsError;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(permissionId)) {
|
||||
reply.status(400);
|
||||
throw new Error("invalid permission id");
|
||||
}
|
||||
|
||||
const permission = await PermissionModel.findOne({
|
||||
_id: new Types.ObjectId(permissionId),
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
if (!permission) {
|
||||
reply.status(404);
|
||||
throw new Error("permission not found");
|
||||
}
|
||||
|
||||
return permission;
|
||||
};
|
||||
|
||||
export const createPermission = async (request: CreatePermissionRequest, reply: FastifyReply): Promise<Permission> => {
|
||||
const [permission, error] = validateEmptyFields(request.body || {}, ["name", "description"]);
|
||||
|
||||
if (error) {
|
||||
reply.status(400);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const newPermission = new PermissionModel(permission);
|
||||
|
||||
return newPermission.save();
|
||||
};
|
||||
|
||||
export const updatePermission = async (
|
||||
request: UpdatePermissionRequest,
|
||||
reply: FastifyReply
|
||||
): Promise<Permission | undefined> => {
|
||||
const [permission, validateBodyError] = validateEmptyFields(request.body || {}, ["name", "description"]);
|
||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params || {}, ["permissionId"]);
|
||||
|
||||
if (validateBodyError) {
|
||||
reply.status(400);
|
||||
throw validateBodyError;
|
||||
}
|
||||
|
||||
if (validateParamsError) {
|
||||
reply.status(400);
|
||||
throw validateParamsError;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(permissionId)) {
|
||||
reply.status(400);
|
||||
throw new Error("invalid permission id");
|
||||
}
|
||||
|
||||
const findedPermission = await PermissionModel.findByIdAndUpdate(permissionId, {
|
||||
$set: {
|
||||
...permission,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!findedPermission) {
|
||||
reply.status(404);
|
||||
throw new Error("permission not found");
|
||||
}
|
||||
|
||||
return permission;
|
||||
};
|
||||
|
||||
export const removePermission = async (
|
||||
request: GetPermissionByIdRequest,
|
||||
reply: FastifyReply
|
||||
): Promise<Permission | undefined> => {
|
||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params || {}, ["permissionId"]);
|
||||
|
||||
if (validateParamsError) {
|
||||
reply.status(400);
|
||||
throw validateParamsError;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(permissionId)) {
|
||||
reply.status(400);
|
||||
throw new Error("invalid permission id");
|
||||
}
|
||||
|
||||
const permission = await PermissionModel.findOneAndUpdate(
|
||||
{
|
||||
_id: new Types.ObjectId(permissionId),
|
||||
isDeleted: false,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
deletedAt: new Date(),
|
||||
isDeleted: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!permission) {
|
||||
reply.status(404);
|
||||
throw new Error("permission not found");
|
||||
}
|
||||
|
||||
return permission;
|
||||
};
|
||||
|
||||
export const restorePermission = async (
|
||||
request: GetPermissionByIdRequest,
|
||||
reply: FastifyReply
|
||||
): Promise<Permission | undefined> => {
|
||||
const [{ permissionId }, validateParamsError] = validateEmptyFields(request.params || {}, ["permissionId"]);
|
||||
|
||||
if (validateParamsError) {
|
||||
reply.status(400);
|
||||
throw validateParamsError;
|
||||
}
|
||||
|
||||
if (!Types.ObjectId.isValid(permissionId)) {
|
||||
reply.status(400);
|
||||
throw new Error("invalid permission id");
|
||||
}
|
||||
|
||||
const permission = await PermissionModel.findOneAndUpdate(
|
||||
{
|
||||
_id: new Types.ObjectId(permissionId),
|
||||
isDeleted: true,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
deletedAt: undefined,
|
||||
updatedAt: new Date(),
|
||||
isDeleted: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!permission) {
|
||||
reply.status(404);
|
||||
throw new Error("permission not found");
|
||||
}
|
||||
|
||||
return permission;
|
||||
};
|
20
src/handlers/permission/types.ts
Normal file
20
src/handlers/permission/types.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { FastifyRequest } from "fastify";
|
||||
import type { Permission } from "@/types/models/permission.type";
|
||||
import type { ObjectWithPossibleFields } from "@/types/object-with-possible-fields";
|
||||
|
||||
export type CreatePermissionRequest = FastifyRequest<{
|
||||
Body?: ObjectWithPossibleFields<Permission>;
|
||||
}>;
|
||||
|
||||
export type UpdatePermissionRequest = FastifyRequest<{
|
||||
Body?: ObjectWithPossibleFields<Permission>;
|
||||
Params?: {
|
||||
permissionId?: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type GetPermissionByIdRequest = FastifyRequest<{
|
||||
Params?: {
|
||||
permissionId?: string;
|
||||
};
|
||||
}>;
|
@ -4,6 +4,9 @@ import { Server } from "./server";
|
||||
|
||||
import { CONFIGURATION } from "@/constants/configuration";
|
||||
|
||||
console.info("server configuration: \n", JSON.stringify(CONFIGURATION, null, 2));
|
||||
console.info("env: \n", JSON.stringify(process.env, null, 2));
|
||||
|
||||
const server = new Server({
|
||||
serverOptions: CONFIGURATION.http,
|
||||
databaseOptions: CONFIGURATION.db,
|
||||
|
@ -8,6 +8,7 @@ const schema: SchemaDefinition<Account> = {
|
||||
userId: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: { unique: true },
|
||||
},
|
||||
nickname: {
|
||||
type: String,
|
||||
|
28
src/models/permission.model.ts
Normal file
28
src/models/permission.model.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Schema, model, SchemaDefinition } from "mongoose";
|
||||
|
||||
import { eloquentSchema } from "./eloquent.schema";
|
||||
|
||||
import type { Permission } from "@/types/models/permission.type";
|
||||
|
||||
const schema: SchemaDefinition<Permission> = {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: { unique: true },
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "Разрешение на создание ролей",
|
||||
},
|
||||
...eloquentSchema,
|
||||
};
|
||||
|
||||
const schemaSettings = {
|
||||
versionKey: false,
|
||||
collection: "permissions",
|
||||
};
|
||||
|
||||
const PermissionSchema = new Schema<Permission>(schema, schemaSettings);
|
||||
|
||||
export const PermissionModel = model("Permission", PermissionSchema);
|
@ -5,9 +5,11 @@ import {
|
||||
getAccountByID,
|
||||
getAccount,
|
||||
setAccountRole,
|
||||
getAllAccounts,
|
||||
getAccounts,
|
||||
deleteAccount,
|
||||
deleteAccountById,
|
||||
removeAccount,
|
||||
removeAccountById,
|
||||
restoreAccount,
|
||||
} from "@/handlers/account";
|
||||
import { verifyUser } from "@/handlers/auth/middleware";
|
||||
@ -19,17 +21,30 @@ import {
|
||||
setAccountRoleSchema,
|
||||
getAccountsSchema,
|
||||
removeAccountSchema,
|
||||
removeAccountByIdSchema,
|
||||
restoreAccountSchema,
|
||||
deleteAccountSchema,
|
||||
deleteAccountByIdSchema,
|
||||
} from "@/swagger/account";
|
||||
|
||||
export const setAccountRoutes = (router: Router): void => {
|
||||
router.get("/all", getAllAccounts, { schema: getAccountsSchema });
|
||||
router.get("/pagination", getAccounts, { schema: getAccountsSchema });
|
||||
router.get("/:userId", getAccountByID, { schema: getAccountByIdSchema });
|
||||
router.get("/", getAccount, { preHandler: [verifyUser], schema: getAccountSchema });
|
||||
|
||||
router.post("/", createAccount, { preHandler: [verifyUser], schema: createAccountSchema });
|
||||
router.post("/restore", restoreAccount, { preHandler: [verifyUser], schema: restoreAccountSchema });
|
||||
|
||||
router.patch("/role", setAccountRole, { preHandler: [verifyUser], schema: setAccountRoleSchema });
|
||||
|
||||
router.delete("/", removeAccount, { preHandler: [verifyUser], schema: removeAccountSchema });
|
||||
router.delete("/:userId", removeAccountById, {
|
||||
preHandler: [verifyUser],
|
||||
schema: removeAccountByIdSchema,
|
||||
});
|
||||
router.delete("/delete", deleteAccount, { preHandler: [verifyUser], schema: deleteAccountSchema });
|
||||
router.delete("/delete/:userId", deleteAccountById, {
|
||||
preHandler: [verifyUser],
|
||||
schema: deleteAccountByIdSchema,
|
||||
});
|
||||
};
|
||||
|
31
src/routes/permission.routes.ts
Normal file
31
src/routes/permission.routes.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Router } from "@/server/router";
|
||||
|
||||
import {
|
||||
getAllPermissions,
|
||||
getPermissionById,
|
||||
createPermission,
|
||||
updatePermission,
|
||||
removePermission,
|
||||
restorePermission,
|
||||
} from "@/handlers/permission";
|
||||
|
||||
import {
|
||||
getAllPermissionsSchema,
|
||||
getPermissionByIdSchema,
|
||||
createPermissionSchema,
|
||||
removePermissionSchema,
|
||||
restorePermissionSchema,
|
||||
updatePermissionSchema,
|
||||
} from "@/swagger/permission";
|
||||
|
||||
export const setPermissionRoutes = (router: Router): void => {
|
||||
router.get("/all", getAllPermissions, { schema: getAllPermissionsSchema });
|
||||
router.get("/:permissionId", getPermissionById, { schema: getPermissionByIdSchema });
|
||||
|
||||
router.post("/", createPermission, { schema: createPermissionSchema });
|
||||
router.post("/restore/:permissionId", restorePermission, { schema: restorePermissionSchema });
|
||||
|
||||
router.patch("/:permissionId", updatePermission, { schema: updatePermissionSchema });
|
||||
|
||||
router.delete("/:permissionId", removePermission, { schema: removePermissionSchema });
|
||||
};
|
16
src/services/account/account.service.ts
Normal file
16
src/services/account/account.service.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { RoleModel } from "@/models/role.model";
|
||||
|
||||
import type { Role } from "@/types/models/role.type";
|
||||
import type { Account } from "@/types/models/account.type";
|
||||
|
||||
export class AccountService {
|
||||
public static async determineAccountRole(account: Account): Promise<Role> {
|
||||
const role = await RoleModel.findOne({ name: account.role }).lean();
|
||||
|
||||
if (!role) {
|
||||
throw new Error("role not found");
|
||||
}
|
||||
|
||||
return role;
|
||||
}
|
||||
}
|
6
src/services/permission/permission.interface.ts
Normal file
6
src/services/permission/permission.interface.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { Account } from "@/types/models/account.type";
|
||||
import type { Role } from "@/types/models/role.type";
|
||||
|
||||
export interface AccountService {
|
||||
determineAccountRole(account: Account): Promise<Role>;
|
||||
}
|
6
src/services/permission/permission.module.ts
Normal file
6
src/services/permission/permission.module.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { AccountService } from "@/services/account/account.service";
|
||||
import { PermissionService } from "./permission.service";
|
||||
|
||||
export const PermissionModule = new PermissionService({
|
||||
accountService: AccountService,
|
||||
});
|
78
src/services/permission/permission.service.test.ts
Normal file
78
src/services/permission/permission.service.test.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { mock, mockReset } from "jest-mock-extended";
|
||||
|
||||
import { PermissionService } from "./permission.service";
|
||||
|
||||
import { PERMISSIONS } from "@/constants/permissions";
|
||||
|
||||
import type { AccountService } from "./permission.interface";
|
||||
import type { Account } from "@/types/models/account.type";
|
||||
|
||||
const DEFAULT_ACCOUNT: Account = {
|
||||
userId: "userId",
|
||||
nickname: "nickname",
|
||||
avatar: "/img/avatar.png",
|
||||
role: "admin",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
isDeleted: false,
|
||||
};
|
||||
|
||||
describe("PermissionService", () => {
|
||||
const accountService = mock<AccountService>();
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(accountService);
|
||||
});
|
||||
|
||||
describe("deleteAccount", () => {
|
||||
test("Успешное определение доступа на удаление аккаунта", async () => {
|
||||
const permissionService = new PermissionService({ accountService });
|
||||
|
||||
accountService.determineAccountRole.mockResolvedValueOnce({
|
||||
name: "admin",
|
||||
permissions: {
|
||||
[PERMISSIONS.deleteAccount]: true,
|
||||
},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
const isAvailableToDeleteAccount = await permissionService.deleteAccount(DEFAULT_ACCOUNT);
|
||||
|
||||
expect(accountService.determineAccountRole).toHaveBeenCalledWith(DEFAULT_ACCOUNT);
|
||||
expect(accountService.determineAccountRole).toHaveBeenCalledTimes(1);
|
||||
expect(isAvailableToDeleteAccount).toBe(true);
|
||||
});
|
||||
|
||||
test("У пользователя нет прав на удаление", async () => {
|
||||
const permissionService = new PermissionService({ accountService });
|
||||
|
||||
accountService.determineAccountRole.mockResolvedValueOnce({
|
||||
name: "admin",
|
||||
permissions: {
|
||||
read: true,
|
||||
},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
const isAvailableToDeleteAccount = await permissionService.deleteAccount(DEFAULT_ACCOUNT);
|
||||
|
||||
expect(accountService.determineAccountRole).toHaveBeenCalledWith(DEFAULT_ACCOUNT);
|
||||
expect(accountService.determineAccountRole).toHaveBeenCalledTimes(1);
|
||||
expect(isAvailableToDeleteAccount).toBe(false);
|
||||
});
|
||||
|
||||
test("Ошибка должна успешно пробрасываться", async () => {
|
||||
const permissionService = new PermissionService({ accountService });
|
||||
|
||||
accountService.determineAccountRole.mockRejectedValueOnce(new Error("role not found"));
|
||||
|
||||
expect(permissionService.deleteAccount(DEFAULT_ACCOUNT)).resolves.toBe(false);
|
||||
expect(accountService.determineAccountRole).toHaveBeenCalledWith(DEFAULT_ACCOUNT);
|
||||
expect(accountService.determineAccountRole).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
39
src/services/permission/permission.service.ts
Normal file
39
src/services/permission/permission.service.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { PermissionModel } from "@/models/permission.model";
|
||||
|
||||
import { PERMISSIONS, PERMISSION_LIST } from "@/constants/permissions";
|
||||
|
||||
import type { Account } from "@/types/models/account.type";
|
||||
import type { AccountService } from "./permission.interface";
|
||||
import type { Permission } from "@/types/models/permission.type";
|
||||
|
||||
type PermissionServiceDeps = {
|
||||
readonly accountService: AccountService;
|
||||
};
|
||||
|
||||
export class PermissionService {
|
||||
private accountService: AccountService;
|
||||
|
||||
constructor(deps: PermissionServiceDeps) {
|
||||
this.accountService = deps.accountService;
|
||||
}
|
||||
|
||||
public async deleteAccount(account: Account): Promise<boolean> {
|
||||
try {
|
||||
const role = await this.accountService.determineAccountRole(account);
|
||||
|
||||
if (!role.permissions[PERMISSIONS.deleteAccount]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async getAllPermissions(): Promise<Permission[]> {
|
||||
const permissions = await PermissionModel.find({}).lean();
|
||||
|
||||
return [...permissions, ...PERMISSION_LIST];
|
||||
}
|
||||
}
|
@ -1,18 +1,19 @@
|
||||
import { getAccountParams, setAccountRoleBody } from "./inputs";
|
||||
import { getAccountParams, setAccountRoleBody, getAccountsQuerystring } from "./inputs";
|
||||
import {
|
||||
getAccountResponse,
|
||||
createAccountResponse,
|
||||
setAccountRoleResponse,
|
||||
getAccountsResponse,
|
||||
removeRoleResponse,
|
||||
removeAccountResponse,
|
||||
} from "./responses";
|
||||
|
||||
import type { SwaggerSchema } from "@/types/swagger.type";
|
||||
|
||||
export const getAccountsSchema: SwaggerSchema = {
|
||||
summary: "Получение информации об аккаунтах",
|
||||
description: "Получение всех аккаунтов из БД",
|
||||
description: "Получение список аккаунтов с пагинацией из БД",
|
||||
tags: ["account"],
|
||||
querystring: getAccountsQuerystring,
|
||||
response: getAccountsResponse,
|
||||
};
|
||||
|
||||
@ -51,7 +52,16 @@ export const removeAccountSchema: SwaggerSchema = {
|
||||
summary: "Удаление аккаунта",
|
||||
description: "Помечает аккаунт удалённым, но не удаляет его из БД",
|
||||
tags: ["account"],
|
||||
response: removeRoleResponse,
|
||||
response: removeAccountResponse,
|
||||
security: [{ bearer: [] }],
|
||||
};
|
||||
|
||||
export const removeAccountByIdSchema: SwaggerSchema = {
|
||||
summary: "Удаление аккаунта по ID",
|
||||
description: "Помечает аккаунт удалённым, но не удаляет его из БД",
|
||||
tags: ["account"],
|
||||
params: getAccountParams,
|
||||
response: removeAccountResponse,
|
||||
security: [{ bearer: [] }],
|
||||
};
|
||||
|
||||
@ -59,7 +69,16 @@ export const deleteAccountSchema: SwaggerSchema = {
|
||||
summary: "Удаление аккаунта",
|
||||
description: "Удаляет аккаунт из БД окончательно",
|
||||
tags: ["account"],
|
||||
response: removeRoleResponse,
|
||||
response: removeAccountResponse,
|
||||
security: [{ bearer: [] }],
|
||||
};
|
||||
|
||||
export const deleteAccountByIdSchema: SwaggerSchema = {
|
||||
summary: "Удаление аккаунта по ID",
|
||||
description: "Удаляет аккаунт из БД окончательно",
|
||||
tags: ["account"],
|
||||
params: getAccountParams,
|
||||
response: removeAccountResponse,
|
||||
security: [{ bearer: [] }],
|
||||
};
|
||||
|
||||
@ -67,6 +86,6 @@ export const restoreAccountSchema: SwaggerSchema = {
|
||||
summary: "Восстановление аккаунта",
|
||||
description: "Восстанавливает аккаунт, который не был удалён окончательно",
|
||||
tags: ["account"],
|
||||
response: removeRoleResponse,
|
||||
response: removeAccountResponse,
|
||||
security: [{ bearer: [] }],
|
||||
};
|
||||
|
@ -11,6 +11,20 @@ export const getAccountParams: SwaggerMessage = {
|
||||
},
|
||||
};
|
||||
|
||||
export const getAccountsQuerystring: SwaggerMessage = {
|
||||
type: "object",
|
||||
properties: {
|
||||
page: {
|
||||
type: "number",
|
||||
description: "номер страницы",
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
description: "Лимит количества аккаунтов (больше 100 не обрабатывается)",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const setAccountRoleBody: SwaggerMessage = {
|
||||
type: "object",
|
||||
required: ["userId", "role"],
|
||||
|
@ -47,3 +47,44 @@ export const account: SwaggerMessage = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const accounts: SwaggerMessage = {
|
||||
description: "Список аккаунтов",
|
||||
type: "object",
|
||||
properties: {
|
||||
accounts: {
|
||||
type: "array",
|
||||
description: "Массив аккаунтов",
|
||||
items: account,
|
||||
},
|
||||
totalPages: { type: "number" },
|
||||
},
|
||||
examples: [
|
||||
{
|
||||
totalPages: 10,
|
||||
accounts: [
|
||||
{
|
||||
_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",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -1,15 +1,11 @@
|
||||
import { swaggerError } from "@/utils/swagger-error";
|
||||
|
||||
import { account } from "./models";
|
||||
import { account, accounts } from "./models";
|
||||
|
||||
import type { SwaggerMessage } from "@/types/swagger.type";
|
||||
|
||||
export const getAccountsResponse: Record<string, SwaggerMessage> = {
|
||||
200: {
|
||||
type: "array",
|
||||
description: "Массив аккаунтов",
|
||||
items: account,
|
||||
},
|
||||
200: accounts,
|
||||
};
|
||||
|
||||
export const getAccountResponse: Record<string, SwaggerMessage> = {
|
||||
@ -32,7 +28,7 @@ export const setAccountRoleResponse: Record<string, SwaggerMessage> = {
|
||||
404: swaggerError(404, "user not found"),
|
||||
};
|
||||
|
||||
export const removeRoleResponse: Record<string, SwaggerMessage> = {
|
||||
export const removeAccountResponse: Record<string, SwaggerMessage> = {
|
||||
200: account,
|
||||
400: swaggerError(400, "invalid user id"),
|
||||
401: swaggerError(401, "invalid token"),
|
||||
|
51
src/swagger/permission/index.ts
Normal file
51
src/swagger/permission/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { getPermissionParams, createPermissionBody } from "./inputs";
|
||||
import { getPermissionResponse, getAllPermissionsResponse, createPermissionResponse } from "./responses";
|
||||
|
||||
import type { SwaggerSchema } from "@/types/swagger.type";
|
||||
|
||||
export const getAllPermissionsSchema: SwaggerSchema = {
|
||||
summary: "Получение информации об разрешениях",
|
||||
description: "Получение списка всех разрешений из БД",
|
||||
tags: ["permission"],
|
||||
response: getAllPermissionsResponse,
|
||||
};
|
||||
|
||||
export const getPermissionByIdSchema: SwaggerSchema = {
|
||||
summary: "Получение информации об аккаунте",
|
||||
description: "Получение аккаунта по ID",
|
||||
tags: ["permission"],
|
||||
params: getPermissionParams,
|
||||
response: getPermissionResponse,
|
||||
};
|
||||
|
||||
export const createPermissionSchema: SwaggerSchema = {
|
||||
summary: "Создание разрешения",
|
||||
tags: ["permission"],
|
||||
body: createPermissionBody,
|
||||
response: createPermissionResponse,
|
||||
};
|
||||
|
||||
export const removePermissionSchema: SwaggerSchema = {
|
||||
summary: "Удаление разрешения",
|
||||
description: "Помечает разрешение удалённым, но не удаляет его из БД",
|
||||
tags: ["permission"],
|
||||
params: getPermissionParams,
|
||||
response: getPermissionResponse,
|
||||
};
|
||||
|
||||
export const restorePermissionSchema: SwaggerSchema = {
|
||||
summary: "Восстановление разрешения",
|
||||
description: "Восстанавливает разрешение, которое не было удалёно окончательно",
|
||||
tags: ["permission"],
|
||||
params: getPermissionParams,
|
||||
response: getPermissionResponse,
|
||||
};
|
||||
|
||||
export const updatePermissionSchema: SwaggerSchema = {
|
||||
summary: "Обновление разрешения",
|
||||
description: "Обновляет данные о разрешении",
|
||||
tags: ["permission"],
|
||||
params: getPermissionParams,
|
||||
body: createPermissionBody,
|
||||
response: getPermissionResponse,
|
||||
};
|
41
src/swagger/permission/inputs.ts
Normal file
41
src/swagger/permission/inputs.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { SwaggerMessage } from "@/types/swagger.type";
|
||||
|
||||
export const getPermissionParams: SwaggerMessage = {
|
||||
type: "object",
|
||||
required: ["permissionId"],
|
||||
properties: {
|
||||
permissionId: {
|
||||
type: "string",
|
||||
description: "ID разрешения",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const getAccountsQuerystring: SwaggerMessage = {
|
||||
type: "object",
|
||||
properties: {
|
||||
page: {
|
||||
type: "number",
|
||||
description: "номер страницы",
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
description: "Лимит количества аккаунтов (больше 100 не обрабатывается)",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const createPermissionBody: SwaggerMessage = {
|
||||
type: "object",
|
||||
required: ["name", "description"],
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
description: "имя разрешения",
|
||||
},
|
||||
description: {
|
||||
type: "string",
|
||||
description: "описание разрешения",
|
||||
},
|
||||
},
|
||||
};
|
42
src/swagger/permission/models.ts
Normal file
42
src/swagger/permission/models.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import type { SwaggerMessage } from "@/types/swagger.type";
|
||||
|
||||
export const permission: SwaggerMessage = {
|
||||
description: "Разрешение",
|
||||
type: "object",
|
||||
properties: {
|
||||
_id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
description: { 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",
|
||||
name: "hub-admin-backend-service.deleteAccount",
|
||||
nickname: "Разрешение на удаление аккаунта",
|
||||
avatar: "/media/avatar/default-avatar.jpg",
|
||||
role: "user",
|
||||
isDeleted: false,
|
||||
createdAt: "2017-07-21T17:32:28Z",
|
||||
updatedAt: "2017-07-21T17:32:28Z",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const permissions: SwaggerMessage = {
|
||||
description: "Список разрешений",
|
||||
type: "array",
|
||||
items: permission,
|
||||
};
|
20
src/swagger/permission/responses.ts
Normal file
20
src/swagger/permission/responses.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { swaggerError } from "@/utils/swagger-error";
|
||||
|
||||
import { permission, permissions } from "./models";
|
||||
|
||||
import type { SwaggerMessage } from "@/types/swagger.type";
|
||||
|
||||
export const getAllPermissionsResponse: Record<string, SwaggerMessage> = {
|
||||
200: permissions,
|
||||
};
|
||||
|
||||
export const getPermissionResponse: Record<string, SwaggerMessage> = {
|
||||
200: permission,
|
||||
400: swaggerError(400, "invalid permission id"),
|
||||
404: swaggerError(400, "permission not found"),
|
||||
};
|
||||
|
||||
export const createPermissionResponse: Record<string, SwaggerMessage> = {
|
||||
200: permission,
|
||||
409: swaggerError(409, "permission already exist"),
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
export type Eloquent = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt: Date;
|
||||
isDeleted: boolean;
|
||||
};
|
||||
export type Eloquent = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt?: Date;
|
||||
isDeleted: boolean;
|
||||
};
|
||||
|
6
src/types/models/permission.type.ts
Normal file
6
src/types/models/permission.type.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { Eloquent } from "./eloquent.type";
|
||||
|
||||
export type Permission = Eloquent & {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
16
src/utils/is-error.test.ts
Normal file
16
src/utils/is-error.test.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { isError } from "./is-error";
|
||||
|
||||
describe("isError", () => {
|
||||
test("Должно возвращать значение true, если это ошибка Error", () => {
|
||||
const error: Error = new Error("An error occurred");
|
||||
|
||||
expect(isError(error)).toBe(true);
|
||||
});
|
||||
|
||||
test("Должно возвращать значение false, если это ошибка Error", () => {
|
||||
expect(isError(undefined)).toBe(false);
|
||||
expect(isError(null)).toBe(false);
|
||||
expect(isError("")).toBe(false);
|
||||
expect(isError({})).toBe(false);
|
||||
});
|
||||
});
|
1
src/utils/is-error.ts
Normal file
1
src/utils/is-error.ts
Normal file
@ -0,0 +1 @@
|
||||
export const isError = (candidate: unknown): candidate is Error => candidate instanceof Error;
|
12
yarn.lock
12
yarn.lock
@ -3943,6 +3943,13 @@ jest-message-util@^29.3.1:
|
||||
slash "^3.0.0"
|
||||
stack-utils "^2.0.3"
|
||||
|
||||
jest-mock-extended@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-3.0.4.tgz#12a5f993d27aa46232012c439a9b7c54f3b0a1fd"
|
||||
integrity sha512-2ynEZ7IEJNrhrgshklDMhrOdnmW4Nt+PhkyRqZxRgpwMo7JjmFWMzyp0+eSyk+H9KK1QjXI5xTZIw6x7cVDcRg==
|
||||
dependencies:
|
||||
ts-essentials "^7.0.3"
|
||||
|
||||
jest-mock@^29.3.1:
|
||||
version "29.3.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e"
|
||||
@ -5604,6 +5611,11 @@ trim-newlines@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
||||
integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
|
||||
|
||||
ts-essentials@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38"
|
||||
integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==
|
||||
|
||||
ts-jest@^29.0.3:
|
||||
version "29.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77"
|
||||
|
Loading…
Reference in New Issue
Block a user