merge rahat

This commit is contained in:
2026-04-15 23:38:15 +06:00
11 changed files with 523 additions and 12 deletions
@@ -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;
+1
View File
@@ -21,4 +21,5 @@ model Account {
profile Profile? //one-to-one profile Profile? //one-to-one
orders Order[] //one-to-many orders Order[] //one-to-many
supports Support[]
} }
+43
View File
@@ -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,
};
+30
View File
@@ -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;
+139
View File
@@ -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,
};
+114
View File
@@ -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,
};
+15 -11
View File
@@ -1,15 +1,19 @@
import { Router } from "express"; import { Router } from "express";
import accountRouter from "./app/modules/account/account.route"; 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 orderRoute from "./app/modules/order/order.route"; import orderRoute from "./app/modules/order/order.route";
import planRoute from "./app/modules/plan/plan.route";
const appRouter = Router(); import profileRoute from "./app/modules/profile/profile.route";
import supportRoute from "./app/modules/support/support.route";
const appRouter = Router();
const moduleRoutes = [ const moduleRoutes = [
{ path: "/order", route: orderRoute }, { path: "/order", route: orderRoute },
{ path: "/support", route: supportRoute },
{ path: "/plan", route: planRoute }, { path: "/plan", route: planRoute },
{ path: "/profile", route: profileRoute },{ path: "/auth", route: accountRouter }]; { path: "/profile", route: profileRoute },
{ path: "/auth", route: accountRouter },
moduleRoutes.forEach((route) => appRouter.use(route.path, route.route)); ];
export default appRouter;
moduleRoutes.forEach((route) => appRouter.use(route.path, route.route));
export default appRouter;
+3 -1
View File
@@ -5,6 +5,7 @@ import { accountSwaggerDocs } from "./app/modules/account/account.swagger";
import { orderSwaggerDocs } from "./app/modules/order/order.swagger"; import { orderSwaggerDocs } from "./app/modules/order/order.swagger";
import { planSwaggerDocs } from "./app/modules/plan/plan.swagger"; import { planSwaggerDocs } from "./app/modules/plan/plan.swagger";
import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger"; import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger";
import { supportSwaggerDocs } from "./app/modules/support/support.swagger";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@@ -19,9 +20,10 @@ export const swaggerOptions = {
}, },
paths: { paths: {
...accountSwaggerDocs, ...accountSwaggerDocs,
...profileSwaggerDocs,
...planSwaggerDocs, ...planSwaggerDocs,
...orderSwaggerDocs, ...orderSwaggerDocs,
...profileSwaggerDocs,
...supportSwaggerDocs,
}, },
servers: servers:
configs.env === "production" configs.env === "production"