12 Commits

Author SHA1 Message Date
abumahid 46401bef0f update 2026-05-23 20:12:09 +06:00
abumahid 61fd639faf feat(account, order, plan, profile, redis): enhance functionality and security
- Updated CORS settings for frontend compatibility.
- Integrated Redis URL configuration.
- Improved login response structure in account service.
- Added role-based authorization for order and plan management.
- Enhanced error handling and logging in profile and plan services.
- Updated Swagger documentation for clarity on order statuses.
- Configured Redis connection for better performance.
2026-04-26 19:14:37 +06:00
abumahid 2d54031c33 🔧 refactor(plan): update imports to use .js extensions and secure delete route with admin auth 2026-04-26 19:10:56 +06:00
abumahid f886c392aa feat(account, queues): enhance email sending and Redis connection settings
- Added attempts and removal options for email queue tasks in `account.service.ts`.
- Updated Redis connection parameters to specify `maxRetriesPerRequest` and disable `enableReadyCheck` in `connection.ts`.
- Introduced an empty line for clarity in the `email.queue.ts` file.
2026-04-26 19:04:34 +06:00
sharafat 107b94bc97 adding the order service delete on the plan api 2026-04-26 18:57:33 +06:00
abumahid e227c42f7d feat(account): update login response and modify CORS origin
- Changed the CORS origin from `http://localhost:3000` to `http://localhost:5173`.
- Updated the login response to return a comprehensive object containing the `accessToken` and user profile data.
- Modified cookie setup to directly use `result.accessToken` instead of `result`.
- Refactored account service to include additional user fields in the login data returned.
- Added `dist` to `.gitignore`.
2026-04-26 18:56:50 +06:00
abumahid 0f7af70b90 ♻️ refactor(account, order, plan, profile, support, email): restructure application modules and enhance error handling
Updated Docker configuration, refactored middleware for improved error handling, and restructured account, order, plan, profile, and support modules, including their routes, services, and validations. Enhanced email processing queues and utilities for token generation, pagination, and response management to streamline the application architecture and enhance maintainability.
2026-04-21 03:12:39 +06:00
abumahid c881efea0f Merge remote-tracking branch 'origin/sharafat' into dev 2026-04-20 20:39:16 +06:00
sharafat 4c1614601a Order API:All routes was created and fully tested 2026-04-19 00:26:50 +06:00
sharafat 1bc1fae274 order api:implement pagination system 2026-04-17 23:36:01 +06:00
abumahid 739e3d1ad6 merge rahat 2026-04-15 23:38:15 +06:00
rahat0078 ba04c54c5b feat(support): complete full CRUD for support module 2026-04-13 00:35:51 +06:00
50 changed files with 894 additions and 211 deletions
+5 -4
View File
@@ -1,8 +1,9 @@
node_modules node_modules
npm-debug.log dist
Dockerfile
.git .git
.gitignore .gitignore
README.md Dockerfile
docker-compose.yml
*.log
.env .env
dist uploads
+2 -1
View File
@@ -4,4 +4,5 @@ node_modules
.env.example .env.example
.env.prod .env.prod
package-lock.json package-lock.json
prisma/generated/ prisma/generated/
dist
+21 -6
View File
@@ -1,25 +1,40 @@
# ---------- BUILD STAGE ---------- # ---------- BUILD STAGE ----------
FROM node:18-alpine AS builder FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
# Only install deps first (cache friendly)
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm ci
# Copy source
COPY . . COPY . .
# Generate prisma + build
RUN npx prisma generate
RUN npm run build RUN npm run build
# ---------- PRODUCTION STAGE ---------- # ---------- PRODUCTION STAGE ----------
FROM node:18-alpine FROM node:20-alpine
WORKDIR /app WORKDIR /app
COPY package*.json ./ ENV NODE_ENV=production
RUN npm install && npm cache clean --force
# Only install production deps
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
# Copy Prisma generated client + schema
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
COPY --from=builder /app/prisma ./prisma
# Copy built app
COPY --from=builder /app/dist ./dist COPY --from=builder /app/dist ./dist
# Uploads folder
RUN mkdir -p /app/uploads RUN mkdir -p /app/uploads
EXPOSE 5000 EXPOSE 5000
+1
View File
@@ -29,6 +29,7 @@
"pg": "^8.20.0", "pg": "^8.20.0",
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1", "swagger-ui-express": "^5.0.1",
"typescript": "^6.0.3",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
@@ -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[]
} }
+2 -1
View File
@@ -2,7 +2,6 @@ model Profile {
id String @id @default(uuid()) id String @id @default(uuid())
accountId String @unique accountId String @unique
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade) account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
shopName String shopName String
shopLogo String? shopLogo String?
contactNumber String? contactNumber String?
@@ -11,3 +10,5 @@ model Profile {
shopCategory String? shopCategory String?
} }
+1 -2
View File
@@ -1,6 +1,5 @@
generator client { generator client {
provider = "prisma-client" provider = "prisma-client-js"
output = "../generated/prisma"
} }
datasource db { datasource db {
+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])
}
+5 -5
View File
@@ -3,10 +3,10 @@ import cors from 'cors';
import express, { Request, Response } from 'express'; import express, { Request, Response } from 'express';
import swaggerJSDoc from 'swagger-jsdoc'; import swaggerJSDoc from 'swagger-jsdoc';
import swaggerUi from "swagger-ui-express"; import swaggerUi from "swagger-ui-express";
import globalErrorHandler from './app/middlewares/global_error_handler'; import globalErrorHandler from './app/middlewares/global_error_handler.js';
import notFound from './app/middlewares/not_found_api'; import notFound from './app/middlewares/not_found_api.js';
import appRouter from './routes'; import appRouter from './routes.js';
import { swaggerOptions } from './swaggerOptions'; import { swaggerOptions } from './swaggerOptions.js';
// define app // define app
const app = express() const app = express()
@@ -15,7 +15,7 @@ app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// middleware // middleware
app.use(cors({ app.use(cors({
origin: ["http://localhost:3000"], origin: ["http://localhost:5173","https://quick-launch-techzaa.vercel.app"],
methods: ["GET", "POST", "PATCH", "DELETE", "PUT"], methods: ["GET", "POST", "PATCH", "DELETE", "PUT"],
credentials: true credentials: true
})) }))
+1
View File
@@ -23,4 +23,5 @@ export const configs = {
cloud_api_key: process.env.CLOUD_API_KEY, cloud_api_key: process.env.CLOUD_API_KEY,
cloud_api_secret: process.env.CLOUD_API_SECRET, cloud_api_secret: process.env.CLOUD_API_SECRET,
}, },
redis_url: process.env.REDIS_URL,
}; };
+1 -1
View File
@@ -1,5 +1,5 @@
import { ZodError, ZodIssue } from 'zod' import { ZodError, ZodIssue } from 'zod'
import { TErrorSources, TGenericErrorResponse } from '../types/error' import { TErrorSources, TGenericErrorResponse } from '../types/error.js'
const handleZodError = (err: ZodError): TGenericErrorResponse => { const handleZodError = (err: ZodError): TGenericErrorResponse => {
const errorSources: TErrorSources = err.issues.map((issue: ZodIssue) => { const errorSources: TErrorSources = err.issues.map((issue: ZodIssue) => {
+2 -1
View File
@@ -1,6 +1,7 @@
import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaPg } from "@prisma/adapter-pg";
import pkg from "@prisma/client";
import "dotenv/config"; import "dotenv/config";
import { PrismaClient } from "../../../prisma/generated/prisma/client"; const { PrismaClient } = pkg;
const connectionString = `${process.env.DATABASE_URL}`; const connectionString = `${process.env.DATABASE_URL}`;
+3 -3
View File
@@ -1,7 +1,7 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { configs } from "../configs"; import { configs } from "../configs/index.js";
import { AppError } from "../utils/app_error"; import { AppError } from "../utils/app_error.js";
import { jwtHelpers, JwtPayloadType } from "../utils/JWT"; import { jwtHelpers, JwtPayloadType } from "../utils/JWT.js";
type Role = "ADMIN" | "USER"; type Role = "ADMIN" | "USER";
+4 -4
View File
@@ -1,9 +1,9 @@
import { ErrorRequestHandler } from "express"; import { ErrorRequestHandler } from "express";
import { ZodError } from "zod"; import { ZodError } from "zod";
import { configs } from "../configs"; import { configs } from "../configs/index.js";
import handleZodError from "../errors/zodError"; import handleZodError from "../errors/zodError.js";
import { TErrorSources } from "../types/error"; import { TErrorSources } from "../types/error.js";
import { AppError } from "../utils/app_error"; import { AppError } from "../utils/app_error.js";
const globalErrorHandler: ErrorRequestHandler = (err, req, res, next) => { const globalErrorHandler: ErrorRequestHandler = (err, req, res, next) => {
let statusCode = 500; let statusCode = 500;
@@ -1,7 +1,7 @@
import { configs } from "../../configs"; import { configs } from "../../configs/index.js";
import catchAsync from "../../utils/catch_async"; import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response"; import manageResponse from "../../utils/manage_response.js";
import { account_services } from "./account.service"; import { account_services } from "./account.service.js";
const create_account = catchAsync(async (req, res) => { const create_account = catchAsync(async (req, res) => {
const result = await account_services.create_account_into_db(req); const result = await account_services.create_account_into_db(req);
@@ -37,7 +37,7 @@ const login_user = catchAsync(async (req, res) => {
const result = await account_services.login_user_into_db(req); const result = await account_services.login_user_into_db(req);
// set access token into cookie // set access token into cookie
res.cookie("access_token", result, { res.cookie("access_token", result.accessToken, {
secure: configs.env === "production", secure: configs.env === "production",
httpOnly: true, httpOnly: true,
}); });
@@ -46,9 +46,7 @@ const login_user = catchAsync(async (req, res) => {
statusCode: 200, statusCode: 200,
success: true, success: true,
message: "User logged in successfully", message: "User logged in successfully",
data: { data: result,
accessToken: result,
},
}); });
}); });
const get_user_account = catchAsync(async (req, res) => { const get_user_account = catchAsync(async (req, res) => {
+4 -4
View File
@@ -1,8 +1,8 @@
import { Router } from "express"; import { Router } from "express";
import auth from "../../middlewares/auth"; import auth from "../../middlewares/auth.js";
import RequestValidator from "../../middlewares/request_validator"; import RequestValidator from "../../middlewares/request_validator.js";
import { account_controller } from "./account.controller"; import { account_controller } from "./account.controller.js";
import { account_validation } from "./account.validation"; import { account_validation } from "./account.validation.js";
const accountRouter = Router(); const accountRouter = Router();
+36 -9
View File
@@ -1,12 +1,12 @@
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { Request } from "express"; import { Request } from "express";
import { configs } from "../../configs"; import { configs } from "../../configs/index.js";
import { prisma } from "../../lib/prisma"; import { prisma } from "../../lib/prisma.js";
import { emailQueue } from "../../queues/email/email.queue"; import { emailQueue } from "../../queues/email/email.queue.js";
import { AppError } from "../../utils/app_error"; import { AppError } from "../../utils/app_error.js";
import { jwtHelpers } from "../../utils/JWT"; import { jwtHelpers } from "../../utils/JWT.js";
import sendMail from "../../utils/mail_sender"; import sendMail from "../../utils/mail_sender.js";
import { otpGenerator } from "../../utils/otpGenerator"; import { otpGenerator } from "../../utils/otpGenerator.js";
const create_account_into_db = async (req: Request) => { const create_account_into_db = async (req: Request) => {
const payload = req?.body; const payload = req?.body;
@@ -22,7 +22,7 @@ const create_account_into_db = async (req: Request) => {
const hashPassword = bcrypt.hashSync(payload.password, 10); const hashPassword = bcrypt.hashSync(payload.password, 10);
// create account and profile // create account and profile
const result = await prisma.$transaction(async (tx) => { const result = await prisma.$transaction(async (tx: any) => {
const account = await tx.account.create({ const account = await tx.account.create({
data: { data: {
email: payload.email, email: payload.email,
@@ -71,6 +71,10 @@ const create_account_into_db = async (req: Request) => {
subject: "Welcome to Quick Launch - Verification OTP", subject: "Welcome to Quick Launch - Verification OTP",
email: payload.email, email: payload.email,
textBody: "You can use otp or verification link for verifying your account" textBody: "You can use otp or verification link for verifying your account"
}, {
attempts: 1,
removeOnComplete: true,
removeOnFail: true,
}) })
return null; return null;
}; };
@@ -160,6 +164,15 @@ const login_user_into_db = async (req: Request) => {
where: { where: {
email: payload.email, email: payload.email,
}, },
select: {
id: true,
email: true,
role: true,
isAccountVerified: true,
isDeleted: true,
password: true,
profile: true,
},
}); });
// check if account exists // check if account exists
@@ -196,7 +209,21 @@ const login_user_into_db = async (req: Request) => {
configs.jwt.access_token as string, configs.jwt.access_token as string,
configs.jwt.access_expires as string, configs.jwt.access_expires as string,
); );
return accessToken;
const finalOutputData = {
id: account.id,
email: account.email,
role: account.role,
shopName: account?.profile?.shopName,
shopLogo: account?.profile?.shopLogo,
}
return {
accessToken,
profile: finalOutputData
};
}; };
const get_user_account_from_db = async (req: Request) => { const get_user_account_from_db = async (req: Request) => {
+3 -3
View File
@@ -1,7 +1,7 @@
import catchAsync from "../../utils/catch_async"; import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response"; import manageResponse from "../../utils/manage_response.js";
import { order_service } from "./order.service"; import { order_service } from "./order.service.js";
const get_all_order = catchAsync(async (req, res) => { const get_all_order = catchAsync(async (req, res) => {
const result = await order_service.get_all_order_from_db(req); const result = await order_service.get_all_order_from_db(req);
+5 -4
View File
@@ -1,8 +1,9 @@
import { Router } from "express"; import { Router } from "express";
import RequestValidator from "../../middlewares/request_validator"; import RequestValidator from "../../middlewares/request_validator.js";
import { order_controller } from "./order.controller"; import { order_controller } from "./order.controller.js";
import { order_validations } from "./order.validation"; import { order_validations } from "./order.validation.js";
import auth from "../../middlewares/auth.js";
const router = Router(); const router = Router();
@@ -14,7 +15,7 @@ router.post(
); );
router.get("/:id", order_controller.get_single_order); router.get("/:id", order_controller.get_single_order);
router.patch( router.patch(
"/:id", "/:id",auth("ADMIN"),
RequestValidator(order_validations.update_order), RequestValidator(order_validations.update_order),
order_controller.update_order, order_controller.update_order,
); );
+124 -56
View File
@@ -1,61 +1,116 @@
import { Request } from "express"; import { Request } from "express";
import { configs } from "../../configs"; import { configs } from "../../configs/index.js";
import { prisma } from "../../lib/prisma"; import { prisma } from "../../lib/prisma.js";
import { orderEmailQueue } from "../../queues/email/order/order.email.queue"; import { orderEmailQueue } from "../../queues/email/order/order.email.queue.js";
import { AppError } from "../../utils/app_error.js";
import paginationHelper from "../../utils/pagination_helper.js";
const get_all_order_from_db = async (req: Request) => { const get_all_order_from_db = async (req: Request) => {
// define your own login here // define your own login here
const search=req.query.search as string const search = req.query.search as string;
const customerName=req.query.customerName as string const customerName = req.query.customerName as string;
const productName=req.query.productName as string const productName = req.query.productName as string;
console.log(productName)
const andCondition=[] as any[]
if(search){
andCondition.push({
OR:[
{
productName:{
contains:search,
mode:"insensitive"
}
}
]
})
}
if(customerName){
andCondition.push({
OR:[
{
customerName:{
contains:customerName,
mode:"insensitive"
}
}
]
})
}
if(productName){
andCondition.push({
OR:[
{
productName:{
contains:productName,
mode:"insensitive"
}
}
]
})
}
console.log(search)
const result = await prisma.order.findMany({ // for date filter
where:{
AND:andCondition const startDate = req.query.startDate as string;
} const endDate = req.query.endDate as string;
const status = (req.query.status as string) || undefined;
const { page, limit, skip, sortBy, sortOrder } = paginationHelper(req.query);
const andCondition = [] as any[];
if (search) {
andCondition.push({
OR: [
{
productName: {
contains: search,
mode: "insensitive",
},
},
],
});
}
if (customerName) {
andCondition.push({
OR: [
{
customerName: {
contains: customerName,
mode: "insensitive",
},
},
],
});
}
if (productName) {
andCondition.push({
OR: [
{
productName: {
contains: productName,
mode: "insensitive",
},
},
],
});
}
if (status) {
andCondition.push({
OR: [
{
status: {
contains: status,
mode: "insensitive",
},
},
],
});
}
// for date filter
const dateFilter: any = {};
if (startDate) {
const start = new Date(startDate);
start.setHours(0, 0, 0, 0);
dateFilter.gte = start;
}
if (endDate) {
const end = new Date(endDate);
end.setHours(23, 59, 59, 999);
dateFilter.lte = end;
}
if (Object.keys(dateFilter).length > 0) {
andCondition.push({
createdAt: dateFilter,
});
}
const getAllOrders = await prisma.order.findMany({
take: limit,
skip,
where: {
AND: andCondition,
},
orderBy: {
[sortBy as string]: sortOrder,
},
}); });
return result; const result = await prisma.order.count({
where: {
AND: andCondition,
},
});
return {
data: getAllOrders,
pagination: {
total: result,
page,
limit,
totalPages: Math.ceil(result / limit),
},
};
}; };
const get_single_order_from_db = async (req: Request) => { const get_single_order_from_db = async (req: Request) => {
@@ -67,9 +122,9 @@ const get_single_order_from_db = async (req: Request) => {
const create_order_into_db = async (req: Request) => { const create_order_into_db = async (req: Request) => {
const payload = req?.body; const payload = req?.body;
console.log(payload) console.log(payload);
payload.status = "INITIATED"; payload.status = "INITIATED";
payload.paymentType = "COD" payload.paymentType = "COD";
// nwo init order // nwo init order
const result = await prisma.order.create({ data: payload }); const result = await prisma.order.create({ data: payload });
@@ -81,15 +136,24 @@ const create_order_into_db = async (req: Request) => {
email: payload.customerEmail, email: payload.customerEmail,
subject: "Order Tracking", subject: "Order Tracking",
textBody: `Your order has been created. Track your order here: ${trackingLink}`, textBody: `Your order has been created. Track your order here: ${trackingLink}`,
htmlBody: `<p>Your order has been created. Track your order here: <a href="${trackingLink}">Track Order</a></p>` htmlBody: `<p>Your order has been created. Track your order here: <a href="${trackingLink}">Track Order</a></p>`,
}) });
} }
return result; return result;
}; };
const update_order_into_db = async (req: Request) => { const update_order_into_db = async (req: Request) => {
// define your own login here // define your own login here
const user = req.user;
console.log(user);
if (user?.role !== "ADMIN") {
throw new AppError("You are not authorized to perform this action", 403);
}
const { id } = req.params as { id: string }; const { id } = req.params as { id: string };
const isProductExist = await prisma.order.findUnique({ where: { id } });
if (!isProductExist) {
throw new AppError("Order is not found", 404);
}
const result = await prisma.order.update({ where: { id }, data: req.body }); const result = await prisma.order.update({ where: { id }, data: req.body });
return result; return result;
}; };
@@ -97,6 +161,10 @@ const update_order_into_db = async (req: Request) => {
const delete_order_from_db = async (req: Request) => { const delete_order_from_db = async (req: Request) => {
// define your own login here // define your own login here
const { id } = req.params as { id: string }; const { id } = req.params as { id: string };
const user = req.user;
if (user?.role !== "ADMIN") {
throw new AppError("You are not authorized to perform this action", 403);
}
const result = await prisma.order.delete({ where: { id } }); const result = await prisma.order.delete({ where: { id } });
return result; return result;
}; };
+43 -23
View File
@@ -1,25 +1,28 @@
export const orderSwaggerDocs = { export const orderSwaggerDocs = {
"/api/order": { "/api/order": {
post: { post: {
tags: ["order"], tags: ["order"],
summary: "Create new order", summary: "Create new order",
description: "", description: ` INITIATED
CONFIRMED
ONGOING
DELIVERED
CANCELLED`,
requestBody: { requestBody: {
required: true, required: true,
content: { content: {
"application/json": { "application/json": {
example: JSON.stringify({ example: JSON.stringify({
"shopAccountId": "", shopAccountId: "",
"productPrice": 1500, productPrice: 1500,
"productQuantity": 2, productQuantity: 2,
"productName": "Wireless Mouse", productName: "Wireless Mouse",
"customerName": "Rahim Uddin", customerName: "Rahim Uddin",
"customerPhone": "+8801712345678", customerPhone: "+8801712345678",
"customerEmail": "softvence.abumahid@gmail.com", customerEmail: "softvence.abumahid@gmail.com",
"customerAddress": "Rangpur, Bangladesh", customerAddress: "Rangpur, Bangladesh",
"customerNote": "Please deliver between 3-5 PM" customerNote: "Please deliver between 3-5 PM",
}) }),
}, },
}, },
}, },
@@ -63,6 +66,32 @@ export const orderSwaggerDocs = {
required: false, required: false,
schema: { type: "string" }, schema: { type: "string" },
}, },
{
name: "status",
in: "query",
required: false,
schema: { type: "string" },
},
{
name: "date",
in: "query",
required: false,
schema: { type: "string" },
},
{
name: "startDate",
in: "query",
required: false,
schema: { type: "string", format: "date" },
example: "2026-04-01",
},
{
name: "endDate",
in: "query",
required: false,
schema: { type: "string", format: "date" },
example: "2026-04-31",
},
], ],
responses: { responses: {
200: { description: "order fetched successfully" }, 200: { description: "order fetched successfully" },
@@ -91,7 +120,7 @@ export const orderSwaggerDocs = {
}, },
patch: { patch: {
tags: ["order"], tags: ["order"],
summary: "Update order", summary: "Update order -(Admin route)",
description: "", description: "",
parameters: [ parameters: [
{ {
@@ -106,14 +135,7 @@ export const orderSwaggerDocs = {
content: { content: {
"application/json": { "application/json": {
example: JSON.stringify({ example: JSON.stringify({
"shopAccountId": "", status: "INITIATED",
"productPrice": 1500,
"productQuantity": 2,
"productName": "Wireless Mouse",
"customerName": "Rahim Uddin",
"customerPhone": "+8801712345678",
"customerAddress": "Rangpur, Bangladesh",
"customerNote": "Please deliver between 3-5 PM"
}), // put your request body }), // put your request body
}, },
}, },
@@ -142,5 +164,3 @@ export const orderSwaggerDocs = {
}, },
}, },
}; };
+3 -3
View File
@@ -1,7 +1,7 @@
import catchAsync from "../../utils/catch_async"; import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response"; import manageResponse from "../../utils/manage_response.js";
import { plan_service } from "./plan.service"; import { plan_service } from "./plan.service.js";
const get_all_plan = catchAsync(async (req, res) => { const get_all_plan = catchAsync(async (req, res) => {
const result = await plan_service.get_all_plan_from_db(req); const result = await plan_service.get_all_plan_from_db(req);
+8 -7
View File
@@ -1,8 +1,8 @@
import { Router } from "express";
import { Router } from "express"; import auth from "../../middlewares/auth.js";
import RequestValidator from "../../middlewares/request_validator"; import RequestValidator from "../../middlewares/request_validator.js";
import { plan_controller } from "./plan.controller"; import { plan_controller } from "./plan.controller.js";
import { plan_validations } from "./plan.validation"; import { plan_validations } from "./plan.validation.js";
const router = Router(); const router = Router();
@@ -10,15 +10,16 @@ router.get("/", plan_controller.get_all_plan);
router.post( router.post(
"/", "/",
RequestValidator(plan_validations.create_plan), RequestValidator(plan_validations.create_plan),
auth("ADMIN"),
plan_controller.create_plan, plan_controller.create_plan,
); );
router.get("/:id", plan_controller.get_single_plan); router.get("/:id", plan_controller.get_single_plan);
router.patch( router.patch(
"/:id", "/:id",
RequestValidator(plan_validations.update_plan), RequestValidator(plan_validations.update_plan),
auth("ADMIN"),
plan_controller.update_plan, plan_controller.update_plan,
); );
router.delete("/:id", plan_controller.delete_plan); router.delete("/:id", auth("ADMIN"), plan_controller.delete_plan);
export default router; export default router;
+3 -3
View File
@@ -1,7 +1,7 @@
import { Request } from "express"; import { Request } from "express";
import { prisma } from "../../lib/prisma"; import { prisma } from "../../lib/prisma.js";
import { AppError } from "../../utils/app_error"; import { AppError } from "../../utils/app_error.js";
const get_all_plan_from_db = async (req: Request) => { const get_all_plan_from_db = async (req: Request) => {
// define your own login here // define your own login here
@@ -22,7 +22,7 @@ const get_single_plan_from_db = async (req: Request) => {
const create_plan_into_db = async (req: Request) => { const create_plan_into_db = async (req: Request) => {
// define your own login here // define your own login here
const user = req.user const user = req?.user
if (user?.role !== "ADMIN") { if (user?.role !== "ADMIN") {
throw new AppError("You dont have permission to create plan information.!!!", 401) throw new AppError("You dont have permission to create plan information.!!!", 401)
} }
@@ -1,6 +1,6 @@
import catchAsync from "../../utils/catch_async"; import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response"; import manageResponse from "../../utils/manage_response.js";
import { profile_service } from "./profile.service"; import { profile_service } from "./profile.service.js";
const update_profile = catchAsync(async (req, res) => { const update_profile = catchAsync(async (req, res) => {
const result = await profile_service.update_profile_into_db(req); const result = await profile_service.update_profile_into_db(req);
+5 -5
View File
@@ -1,9 +1,9 @@
import { Router } from "express"; import { Router } from "express";
import RequestValidator from "../../middlewares/request_validator"; import RequestValidator from "../../middlewares/request_validator.js";
import { profile_controller } from "./profile.controller"; import { profile_controller } from "./profile.controller.js";
import { profile_validations } from "./profile.validation"; import { profile_validations } from "./profile.validation.js";
import auth from "../../middlewares/auth"; import auth from "../../middlewares/auth.js";
import uploader from "../../middlewares/uploader"; import uploader from "../../middlewares/uploader.js";
const router = Router(); const router = Router();
+4 -3
View File
@@ -1,12 +1,13 @@
import { Request } from "express"; import { Request } from "express";
import uploadCloud from "../../utils/cloudinary"; import uploadCloud from "../../utils/cloudinary.js";
import { prisma } from "../../lib/prisma"; import { prisma } from "../../lib/prisma.js";
import { JwtPayloadType } from "../../utils/JWT"; import { JwtPayloadType } from "../../utils/JWT.js";
const update_profile_into_db = async (req: Request) => { const update_profile_into_db = async (req: Request) => {
const user = req?.user as JwtPayloadType; const user = req?.user as JwtPayloadType;
const payload = req?.body; const payload = req?.body;
const file = req?.file; const file = req?.file;
console.log(payload);
// check file and upload to cloud // check file and upload to cloud
if (file) { if (file) {
const cloudRes = await uploadCloud(file); const cloudRes = await uploadCloud(file);
@@ -0,0 +1,99 @@
import { Request, Response } from "express";
import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response.js";
import { support_service } from "./support.service.js";
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.js";
import { support_controller } from "./support.controller.js";
import { support_validations } from "./support.validation.js";
import auth from "../../middlewares/auth.js";
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.js";
import { Prisma } from "@prisma/client";
import { AppError } from "../../utils/app_error.js";
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,
};
+7 -5
View File
@@ -1,6 +1,8 @@
import { QueueOptions } from "bullmq"; import { Redis } from "ioredis";
import { configs } from "../configs/index.js";
export const redisConnection: QueueOptions["connection"] = { export const redisConnection = new Redis(configs.redis_url as string, {
host: "127.0.0.1", tls: {},
port: 6379, maxRetriesPerRequest: null,
}; enableReadyCheck: false,
});
+3 -3
View File
@@ -1,6 +1,6 @@
import { otpTemplate } from "../../templates/otpTemplate"; import { otpTemplate } from "../../templates/otpTemplate.js";
import sendMail from "../../utils/mail_sender"; import sendMail from "../../utils/mail_sender.js";
import { TEmailQueue } from "./email.queue"; import { TEmailQueue } from "./email.queue.js";
// email.processor.ts // email.processor.ts
export const emailProcessor = async (job: any) => { export const emailProcessor = async (job: any) => {
+2 -1
View File
@@ -1,6 +1,6 @@
// email.queue.ts // email.queue.ts
import { Queue } from "bullmq"; import { Queue } from "bullmq";
import { redisConnection } from "../connection"; import { redisConnection } from "../connection.js";
export type TEmailQueue = { export type TEmailQueue = {
email: string; email: string;
@@ -13,4 +13,5 @@ export type TEmailQueue = {
export const emailQueue = new Queue<TEmailQueue>("email-queue", { export const emailQueue = new Queue<TEmailQueue>("email-queue", {
connection: redisConnection, connection: redisConnection,
}); });
+3 -3
View File
@@ -1,8 +1,8 @@
// email.worker.ts // email.worker.ts
import { Worker } from "bullmq"; import { Worker } from "bullmq";
import { redisConnection } from "../connection"; import { redisConnection } from "../connection.js";
import { emailProcessor } from "./email.processor"; import { emailProcessor } from "./email.processor.js";
import { TEmailQueue } from "./email.queue"; import { TEmailQueue } from "./email.queue.js";
export const emailWorker = new Worker<TEmailQueue>( export const emailWorker = new Worker<TEmailQueue>(
"email-queue", "email-queue",
@@ -1,5 +1,5 @@
import sendMail from "../../../utils/mail_sender"; import sendMail from "../../../utils/mail_sender.js";
import { TOrderEmailQueue } from "./order.email.queue"; import { TOrderEmailQueue } from "./order.email.queue.js";
// email.processor.ts // email.processor.ts
export const orderEmailProcessor = async (job: any) => { export const orderEmailProcessor = async (job: any) => {
@@ -1,5 +1,5 @@
import { Queue } from "bullmq"; import { Queue } from "bullmq";
import { redisConnection } from "../../connection"; import { redisConnection } from "../../connection.js";
export type TOrderEmailQueue = { export type TOrderEmailQueue = {
email: string; email: string;
@@ -1,8 +1,8 @@
// email.worker.ts // email.worker.ts
import { Worker } from "bullmq"; import { Worker } from "bullmq";
import { redisConnection } from "../../connection"; import { redisConnection } from "../../connection.js";
import { orderEmailProcessor } from "./order.email.processor"; import { orderEmailProcessor } from "./order.email.processor.js";
import { TOrderEmailQueue } from "./order.email.queue"; import { TOrderEmailQueue } from "./order.email.queue.js";
export const emailWorker = new Worker<TOrderEmailQueue>( export const emailWorker = new Worker<TOrderEmailQueue>(
+2 -2
View File
@@ -1,4 +1,4 @@
import "./email/email.worker"; import "./email/email.worker.js";
import "./email/order/order.email.worker"; import "./email/order/order.email.worker.js";
console.log("Workers running..."); console.log("Workers running...");
+1 -1
View File
@@ -1,4 +1,4 @@
import { TEmailQueue } from "../queues/email/email.queue" import { TEmailQueue } from "../queues/email/email.queue.js"
export const otpTemplate = (payload: TEmailQueue) => { export const otpTemplate = (payload: TEmailQueue) => {
return ` return `
+1 -1
View File
@@ -1,6 +1,6 @@
import { v2 as cloudinary } from 'cloudinary'; import { v2 as cloudinary } from 'cloudinary';
import fs from 'fs'; import fs from 'fs';
import { configs } from '../configs'; import { configs } from '../configs/index.js';
type ICloudinaryResponse = { type ICloudinaryResponse = {
asset_id: string; asset_id: string;
+1 -1
View File
@@ -1,5 +1,5 @@
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { configs } from '../configs'; import { configs } from '../configs/index.js';
type TMailContent = { type TMailContent = {
to: string, to: string,
subject: string, subject: string,
+34
View File
@@ -0,0 +1,34 @@
// options type
type IOptions = {
page?: number | string;
limit?: number | string;
sortOrder?: string;
sortBy?: string;
};
// return type
export type IPaginationResult = {
page: number;
limit: number;
skip: number;
sortOrder?: string;
sortBy?: string;
};
const paginationHelper = (options:IOptions): IPaginationResult => {
const page = Number(options?.page) || 1;
const limit = Number(options?.limit) || 10;
const skip = (page-1)*limit
const sortBy =options?.sortBy ||"createdAt";
const sortOrder = options?.sortOrder || "desc";
return {
page,
limit,
skip,
sortBy,
sortOrder
}
};
export default paginationHelper;
+16 -12
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.js";
import profileRoute from "./app/modules/profile/profile.route"; import orderRoute from "./app/modules/order/order.route.js";
import planRoute from "./app/modules/plan/plan.route"; import planRoute from "./app/modules/plan/plan.route.js";
import orderRoute from "./app/modules/order/order.route"; import profileRoute from "./app/modules/profile/profile.route.js";
import supportRoute from "./app/modules/support/support.route.js";
const appRouter = Router();
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;
+4 -4
View File
@@ -1,7 +1,7 @@
import app from "./app"; import app from "./app.js";
import { configs } from "./app/configs/index"; import { configs } from "./app/configs/index.js";
import { prisma } from "./app/lib/prisma"; import { prisma } from "./app/lib/prisma.js";
import "./app/queues/worker"; import "./app/queues/worker.js";
async function main() { async function main() {
try { try {
+10 -8
View File
@@ -1,10 +1,11 @@
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import path from "path"; import path from "path";
import { configs } from "./app/configs"; import { configs } from "./app/configs/index.js";
import { accountSwaggerDocs } from "./app/modules/account/account.swagger"; import { accountSwaggerDocs } from "./app/modules/account/account.swagger.js";
import { orderSwaggerDocs } from "./app/modules/order/order.swagger"; import { orderSwaggerDocs } from "./app/modules/order/order.swagger.js";
import { planSwaggerDocs } from "./app/modules/plan/plan.swagger"; import { planSwaggerDocs } from "./app/modules/plan/plan.swagger.js";
import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger"; import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger.js";
import { supportSwaggerDocs } from "./app/modules/support/support.swagger.js";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@@ -19,14 +20,15 @@ export const swaggerOptions = {
}, },
paths: { paths: {
...accountSwaggerDocs, ...accountSwaggerDocs,
...profileSwaggerDocs,
...planSwaggerDocs, ...planSwaggerDocs,
...orderSwaggerDocs, ...orderSwaggerDocs,
...profileSwaggerDocs,
...supportSwaggerDocs,
}, },
servers: servers:
configs.env === "production" configs.env === "production"
? [{ url: "https://live-url.com" }, { url: "http://localhost:5000" }] ? [{ url: "https://quicklunch-server.onrender.com" }, { url: "http://localhost:5000" }]
: [{ url: "http://localhost:5000" }, { url: "https://live-url.com" }], : [{ url: "http://localhost:5000" }, { url: "https://quicklunch-server.onrender.com" }],
components: { components: {
securitySchemes: { securitySchemes: {
AuthorizationToken: { AuthorizationToken: {
+4 -5
View File
@@ -1,9 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2023", "target": "ES2023",
"module": "ESNext", "module": "nodenext",
"moduleResolution": "bundler", "moduleResolution": "nodenext",
"rootDir": "./", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
@@ -11,8 +11,7 @@
"skipLibCheck": true "skipLibCheck": true
}, },
"include": [ "include": [
"src/**/*", "src/**/*"
"prisma/**/*"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",