feat(support): complete full CRUD for support module
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "T_SupportType" AS ENUM ('TECHNICAL', 'BILLING', 'DOMAIN', 'TEMPLATE', 'PAYMENT', 'ACCOUNT', 'FEATURE_REQUEST', 'BUG', 'OTHER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "T_SupportStatus" AS ENUM ('OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Support" (
|
||||
"id" TEXT NOT NULL,
|
||||
"issueName" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"type" "T_SupportType" NOT NULL,
|
||||
"status" "T_SupportStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"resolvedBy" TEXT,
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"storeAccountId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Support_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Support_storeAccountId_idx" ON "Support"("storeAccountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Support_storeAccountId_status_idx" ON "Support"("storeAccountId", "status");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Support" ADD CONSTRAINT "Support_storeAccountId_fkey" FOREIGN KEY ("storeAccountId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The values [CLOSED] on the enum `T_SupportStatus` will be removed. If these variants are still used in the database, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterEnum
|
||||
BEGIN;
|
||||
CREATE TYPE "T_SupportStatus_new" AS ENUM ('OPEN', 'IN_PROGRESS', 'RESOLVED', 'REJECTED');
|
||||
ALTER TABLE "public"."Support" ALTER COLUMN "status" DROP DEFAULT;
|
||||
ALTER TABLE "Support" ALTER COLUMN "status" TYPE "T_SupportStatus_new" USING ("status"::text::"T_SupportStatus_new");
|
||||
ALTER TYPE "T_SupportStatus" RENAME TO "T_SupportStatus_old";
|
||||
ALTER TYPE "T_SupportStatus_new" RENAME TO "T_SupportStatus";
|
||||
DROP TYPE "public"."T_SupportStatus_old";
|
||||
ALTER TABLE "Support" ALTER COLUMN "status" SET DEFAULT 'OPEN';
|
||||
COMMIT;
|
||||
@@ -18,5 +18,7 @@ model Account {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now())
|
||||
|
||||
supports Support[]
|
||||
|
||||
profile Profile?
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
enum T_SupportType {
|
||||
TECHNICAL
|
||||
BILLING
|
||||
DOMAIN
|
||||
TEMPLATE
|
||||
PAYMENT
|
||||
ACCOUNT
|
||||
FEATURE_REQUEST
|
||||
BUG
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum T_SupportStatus {
|
||||
OPEN
|
||||
IN_PROGRESS
|
||||
RESOLVED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
|
||||
|
||||
model Support {
|
||||
id String @id @default(uuid())
|
||||
|
||||
issueName String
|
||||
description String
|
||||
|
||||
type T_SupportType
|
||||
status T_SupportStatus @default(OPEN)
|
||||
|
||||
resolvedBy String?
|
||||
resolvedAt DateTime?
|
||||
|
||||
storeAccountId String
|
||||
|
||||
storeAccount Account @relation(fields: [storeAccountId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([storeAccountId])
|
||||
@@index([storeAccountId, status])
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
|
||||
import { Request, Response } from "express";
|
||||
import catchAsync from "../../utils/catch_async";
|
||||
import manageResponse from "../../utils/manage_response";
|
||||
import { support_service } from "./support.service";
|
||||
|
||||
|
||||
|
||||
const createSupport = catchAsync(async (req: Request, res: Response) => {
|
||||
const id = req?.user?.accountId;
|
||||
const data = {
|
||||
...req.body,
|
||||
storeAccountId: id as string
|
||||
}
|
||||
|
||||
const result = await support_service.createSupportIntoDB(data);
|
||||
manageResponse(res, {
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
message: "support created successfully.",
|
||||
data: result,
|
||||
meta: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const getAllSupport = catchAsync(async (req: Request, res: Response) => {
|
||||
const role = req?.user?.role;
|
||||
const id = req?.user?.accountId;
|
||||
const search = req?.query?.search;
|
||||
const type = req?.query?.type;
|
||||
const status = req?.query?.status;
|
||||
|
||||
const result = await support_service.getAllSupportFromDB(id as string, role as string, search as string, type as string, status as string);
|
||||
manageResponse(res, {
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
message: "All support fetched successfully.",
|
||||
data: result,
|
||||
meta: {},
|
||||
});
|
||||
});
|
||||
|
||||
const get_single_support = catchAsync(async (req, res) => {
|
||||
const {id} = req.params;
|
||||
const userId = req?.user?.accountId;
|
||||
const role = req?.user?.role
|
||||
|
||||
|
||||
const result = await support_service.getSingleSupportFromDB(id as string, userId as string, role as string);
|
||||
manageResponse(res, {
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
message: "Single support fetched successfully.",
|
||||
data: result,
|
||||
meta: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const update_support = catchAsync(async (req, res) => {
|
||||
const {id} = req.params;
|
||||
const userId = req?.user?.accountId;
|
||||
const role = req?.user?.role;
|
||||
const data = req.body
|
||||
|
||||
|
||||
const result = await support_service.updateSupportIntoDB(id as string, userId as string, role as string, data);
|
||||
manageResponse(res, {
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
message: "support updated successfully.",
|
||||
data: result,
|
||||
meta: {},
|
||||
});
|
||||
});
|
||||
|
||||
const delete_support = catchAsync(async (req, res) => {
|
||||
const {id} = req.params;
|
||||
const userId = req?.user?.accountId;
|
||||
const role = req?.user?.role
|
||||
|
||||
const result = await support_service.deleteSupportFromDB(id as string, userId as string, role as string);
|
||||
manageResponse(res, {
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
message: "support deleted successfully.",
|
||||
data: result,
|
||||
meta: {},
|
||||
});
|
||||
});
|
||||
|
||||
export const support_controller = {
|
||||
createSupport,
|
||||
getAllSupport,
|
||||
get_single_support,
|
||||
update_support,
|
||||
delete_support,
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
import { Router } from "express";
|
||||
import RequestValidator from "../../middlewares/request_validator";
|
||||
import { support_controller } from "./support.controller";
|
||||
import { support_validations } from "./support.validation";
|
||||
import auth from "../../middlewares/auth";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", auth("ADMIN", "USER"), support_controller.getAllSupport);
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
auth("ADMIN", "USER"),
|
||||
RequestValidator(support_validations.create_support),
|
||||
support_controller.createSupport,
|
||||
);
|
||||
|
||||
router.get("/:id", auth("ADMIN", "USER"), support_controller.get_single_support);
|
||||
router.patch(
|
||||
"/:id",
|
||||
auth("ADMIN", "USER"),
|
||||
RequestValidator(support_validations.update_support),
|
||||
support_controller.update_support,
|
||||
);
|
||||
|
||||
router.delete("/:id", auth("ADMIN", "USER"), support_controller.delete_support);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { prisma } from "../../lib/prisma";
|
||||
import { Prisma } from "../../../../prisma/generated/prisma/client";
|
||||
import { AppError } from "../../utils/app_error";
|
||||
|
||||
const createSupportIntoDB = async (payload: any) => {
|
||||
const result = await prisma.support.create({ data: payload });
|
||||
return result;
|
||||
};
|
||||
|
||||
const getAllSupportFromDB = async (
|
||||
user_id: string,
|
||||
role: string,
|
||||
search?: string,
|
||||
type?: string,
|
||||
status?: string,
|
||||
) => {
|
||||
const andCondition: Prisma.SupportWhereInput[] = [];
|
||||
|
||||
if (search) {
|
||||
andCondition.push({
|
||||
OR: [
|
||||
{
|
||||
issueName: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (type) {
|
||||
andCondition.push({
|
||||
type: type as any,
|
||||
});
|
||||
}
|
||||
|
||||
if (status) {
|
||||
andCondition.push({
|
||||
status: status as any,
|
||||
});
|
||||
}
|
||||
|
||||
if (role !== "ADMIN") {
|
||||
andCondition.push({
|
||||
storeAccountId: user_id,
|
||||
});
|
||||
}
|
||||
|
||||
const whereCondition: Prisma.SupportWhereInput =
|
||||
andCondition.length > 0 ? { AND: andCondition } : {};
|
||||
|
||||
const result = await prisma.support.findMany({
|
||||
where: whereCondition,
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getSingleSupportFromDB = async (
|
||||
id: string,
|
||||
userId: string,
|
||||
role: string,
|
||||
) => {
|
||||
const support = await prisma.support.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (!support) {
|
||||
throw new AppError("Support not found", 404);
|
||||
}
|
||||
|
||||
if (role !== "ADMIN" && support.storeAccountId !== userId) {
|
||||
throw new AppError("You are not authorized", 403);
|
||||
}
|
||||
return support;
|
||||
};
|
||||
|
||||
const updateSupportIntoDB = async (
|
||||
id: string,
|
||||
userId: string,
|
||||
role: string,
|
||||
payload: any,
|
||||
) => {
|
||||
const support = await prisma.support.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!support) {
|
||||
throw new AppError("Support not found", 404);
|
||||
}
|
||||
|
||||
if (role !== "ADMIN" && support.storeAccountId !== userId) {
|
||||
throw new AppError("You are not authorized", 403);
|
||||
}
|
||||
|
||||
const result = await prisma.support.update({
|
||||
where: { id },
|
||||
data: payload,
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const deleteSupportFromDB = async (
|
||||
id: string,
|
||||
userId: string,
|
||||
role: string,
|
||||
) => {
|
||||
const support = await prisma.support.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (!support) {
|
||||
throw new AppError("Support not found", 404);
|
||||
}
|
||||
if (role !== "ADMIN" && support.storeAccountId !== userId) {
|
||||
throw new AppError("You are not authorized", 403);
|
||||
}
|
||||
|
||||
const result = await prisma.support.delete({
|
||||
where: {id}
|
||||
})
|
||||
return result;
|
||||
};
|
||||
|
||||
export const support_service = {
|
||||
createSupportIntoDB,
|
||||
getAllSupportFromDB,
|
||||
getSingleSupportFromDB,
|
||||
updateSupportIntoDB,
|
||||
deleteSupportFromDB,
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
|
||||
export const supportSwaggerDocs = {
|
||||
"/api/support": {
|
||||
post: {
|
||||
tags: ["support"],
|
||||
summary: "Create new support",
|
||||
description: "",
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
example: JSON.stringify({
|
||||
"issueName": "Your issue name",
|
||||
"description": "Issue description",
|
||||
"type": "Issue Type"
|
||||
}), // put your request body
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
201: { description: "support created successfully" },
|
||||
500: { description: "Validation error or internal server error" },
|
||||
},
|
||||
},
|
||||
get: {
|
||||
tags: ["support"],
|
||||
summary: "Get all support",
|
||||
description: "",
|
||||
parameters: [
|
||||
{
|
||||
name: "page",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: { type: "number" },
|
||||
},
|
||||
{
|
||||
name: "limit",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: { type: "number" },
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: { description: "support fetched successfully" },
|
||||
401: { description: "unauthorized" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"/api/support/{id}": {
|
||||
get: {
|
||||
tags: ["support"],
|
||||
summary: "Get single support",
|
||||
description: "",
|
||||
parameters: [
|
||||
{
|
||||
name: "id",
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: { type: "string" },
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: { description: "support fetched successfully" },
|
||||
401: { description: "unauthorized" },
|
||||
},
|
||||
},
|
||||
patch: {
|
||||
tags: ["support"],
|
||||
summary: "Update support",
|
||||
description: "",
|
||||
parameters: [
|
||||
{
|
||||
name: "id",
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: { type: "string" },
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
example: JSON.stringify({}), // put your request body
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: { description: "support updated successfully" },
|
||||
500: { description: "Validation error or internal server error" },
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
tags: ["support"],
|
||||
summary: "Delete support",
|
||||
description: "",
|
||||
parameters: [
|
||||
{
|
||||
name: "id",
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: { type: "string" },
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: { description: "support delete successfully" },
|
||||
401: { description: "unauthorized" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const create_support = z.object({
|
||||
issueName: z.string().min(1, "issueName is required"),
|
||||
description: z.string().min(1, "description is required"),
|
||||
type: z.enum([
|
||||
"TECHNICAL",
|
||||
"BILLING",
|
||||
"DOMAIN",
|
||||
"TEMPLATE",
|
||||
"PAYMENT",
|
||||
"ACCOUNT",
|
||||
"FEATURE_REQUEST",
|
||||
"BUG",
|
||||
"OTHER",
|
||||
]),
|
||||
});
|
||||
|
||||
const update_support = z.object({
|
||||
issueName: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
type: z.enum(["BUG", "PAYMENT", "ACCOUNT", "OTHER"]).optional(),
|
||||
|
||||
status: z.enum(["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"]).optional(),
|
||||
|
||||
resolvedBy: z.string().optional(),
|
||||
resolvedAt: z.coerce.date().optional(),
|
||||
});
|
||||
|
||||
export const support_validations = {
|
||||
create_support,
|
||||
update_support,
|
||||
};
|
||||
@@ -2,10 +2,12 @@ import { Router } from "express";
|
||||
import accountRouter from "./app/modules/account/account.route";
|
||||
import profileRoute from "./app/modules/profile/profile.route";
|
||||
import planRoute from "./app/modules/plan/plan.route";
|
||||
import supportRoute from "./app/modules/support/support.route";
|
||||
|
||||
const appRouter = Router();
|
||||
|
||||
const moduleRoutes = [
|
||||
{ path: "/support", route: supportRoute },
|
||||
{ path: "/plan", route: planRoute },
|
||||
{ path: "/profile", route: profileRoute },{ path: "/auth", route: accountRouter }];
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { configs } from "./app/configs";
|
||||
import { accountSwaggerDocs } from "./app/modules/account/account.swagger";
|
||||
import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger";
|
||||
import { planSwaggerDocs } from "./app/modules/plan/plan.swagger";
|
||||
import { supportSwaggerDocs } from "./app/modules/support/support.swagger";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -20,6 +21,7 @@ export const swaggerOptions = {
|
||||
...accountSwaggerDocs,
|
||||
...profileSwaggerDocs,
|
||||
...planSwaggerDocs,
|
||||
...supportSwaggerDocs,
|
||||
},
|
||||
servers:
|
||||
configs.env === "production"
|
||||
|
||||
Reference in New Issue
Block a user