20 Commits

Author SHA1 Message Date
abumahid 7bf4bc93f1 Merge pull request 'Sharafat' (#11) from sharafat into main
Reviewed-on: #11
2026-06-17 16:14:29 +00:00
sharafat f224ff6bf0 Add api: Users 2026-06-17 22:01:03 +06:00
abumahid 4468c00abf Merge pull request 'Remove dist folder from repository' (#1) from enh/remove-dist into main
Reviewed-on: #1
2026-06-17 14:18:19 +00:00
abumahid 0a8fe573ae Remove dist folder from repository 2026-06-17 20:15:51 +06:00
sharafat c05266a522 USER API's resolve the issues 2026-05-24 23:37:13 +06:00
sharafat 1abecc9b8f Update:PROFILE API's and create get users api's with pagination 2026-05-24 00:17:16 +06:00
sharafat d2b320f3b1 Change the api and add some new feature 2026-04-30 21:26:18 +06:00
sharafat b09fdfc255 Template-API's : create tamplate and also add the get all template and get single templates 2026-04-28 00:29:01 +06:00
sharafat 47d30d96eb Template API: create database schema 2026-04-27 00:03:06 +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
68 changed files with 2245 additions and 218 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;
@@ -0,0 +1,207 @@
-- CreateTable
CREATE TABLE "Template" (
"id" TEXT NOT NULL,
"language" TEXT NOT NULL,
"deliveryCharge" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Template_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Banner" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"bannerTitle" TEXT NOT NULL,
"bannerDesc" TEXT NOT NULL,
"bannerImage" TEXT NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "Banner_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Address" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"phoneNumber" TEXT NOT NULL,
"shopLocation" TEXT NOT NULL,
"shopEmail" TEXT NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "Address_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Ingredient" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "Ingredient_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ingredientOptions" (
"id" TEXT NOT NULL,
"ingrImg" TEXT NOT NULL,
"ingrTitle" TEXT NOT NULL,
"ingrDes" TEXT NOT NULL,
"ingredientId" TEXT NOT NULL,
CONSTRAINT "ingredientOptions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Instruction" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"instBanner" TEXT NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "Instruction_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InstructionOptions" (
"id" TEXT NOT NULL,
"hint" TEXT NOT NULL,
"detail" TEXT NOT NULL,
"instructionId" TEXT NOT NULL,
CONSTRAINT "InstructionOptions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FAQ" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "FAQ_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FAQOptions" (
"id" TEXT NOT NULL,
"index" INTEGER NOT NULL,
"text" TEXT NOT NULL,
"faqId" TEXT NOT NULL,
CONSTRAINT "FAQOptions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Tips" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"tipsBanner" TEXT NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "Tips_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TipsOption" (
"id" TEXT NOT NULL,
"index" INTEGER NOT NULL,
"text" TEXT NOT NULL,
"tipsId" TEXT NOT NULL,
CONSTRAINT "TipsOption_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PriceCombo" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "PriceCombo_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PriceOption" (
"id" TEXT NOT NULL,
"quantity" TEXT NOT NULL,
"price" TEXT NOT NULL,
"comboId" TEXT NOT NULL,
CONSTRAINT "PriceOption_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"isVisible" BOOLEAN NOT NULL,
"price" TEXT NOT NULL,
"discount" INTEGER NOT NULL,
"productName" TEXT NOT NULL,
"templateId" TEXT NOT NULL,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Banner_templateId_key" ON "Banner"("templateId");
-- CreateIndex
CREATE UNIQUE INDEX "Address_templateId_key" ON "Address"("templateId");
-- CreateIndex
CREATE UNIQUE INDEX "Ingredient_templateId_key" ON "Ingredient"("templateId");
-- CreateIndex
CREATE UNIQUE INDEX "Instruction_templateId_key" ON "Instruction"("templateId");
-- CreateIndex
CREATE UNIQUE INDEX "FAQ_templateId_key" ON "FAQ"("templateId");
-- CreateIndex
CREATE UNIQUE INDEX "Tips_templateId_key" ON "Tips"("templateId");
-- CreateIndex
CREATE UNIQUE INDEX "PriceCombo_templateId_key" ON "PriceCombo"("templateId");
-- CreateIndex
CREATE UNIQUE INDEX "Product_templateId_key" ON "Product"("templateId");
-- AddForeignKey
ALTER TABLE "Banner" ADD CONSTRAINT "Banner_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Address" ADD CONSTRAINT "Address_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ingredient" ADD CONSTRAINT "Ingredient_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ingredientOptions" ADD CONSTRAINT "ingredientOptions_ingredientId_fkey" FOREIGN KEY ("ingredientId") REFERENCES "Ingredient"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Instruction" ADD CONSTRAINT "Instruction_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InstructionOptions" ADD CONSTRAINT "InstructionOptions_instructionId_fkey" FOREIGN KEY ("instructionId") REFERENCES "Instruction"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FAQ" ADD CONSTRAINT "FAQ_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FAQOptions" ADD CONSTRAINT "FAQOptions_faqId_fkey" FOREIGN KEY ("faqId") REFERENCES "FAQ"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Tips" ADD CONSTRAINT "Tips_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TipsOption" ADD CONSTRAINT "TipsOption_tipsId_fkey" FOREIGN KEY ("tipsId") REFERENCES "Tips"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PriceCombo" ADD CONSTRAINT "PriceCombo_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PriceOption" ADD CONSTRAINT "PriceOption_comboId_fkey" FOREIGN KEY ("comboId") REFERENCES "PriceCombo"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,14 @@
-- CreateEnum
CREATE TYPE "ShopStatus" AS ENUM ('ACTIVE', 'SUSPENDED', 'DELETED');
-- AlterTable
ALTER TABLE "Profile" ADD COLUMN "status" "ShopStatus" NOT NULL DEFAULT 'ACTIVE';
-- CreateTable
CREATE TABLE "Users" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Users_pkey" PRIMARY KEY ("id")
);
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "ShopStatus" ADD VALUE 'INACTIVE';
+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[]
} }
+11 -5
View File
@@ -1,13 +1,19 @@
model Profile { enum ShopStatus {
id String @id @default(uuid()) ACTIVE
accountId String @unique SUSPENDED
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade) DELETED
INACTIVE
}
model Profile {
id String @id @default(uuid())
accountId String @unique
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
shopName String shopName String
shopLogo String? shopLogo String?
contactNumber String? contactNumber String?
shopAddress String? shopAddress String?
shopMapLocation String? shopMapLocation String?
shopCategory String? shopCategory String?
status ShopStatus @default(ACTIVE)
} }
+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])
}
+139
View File
@@ -0,0 +1,139 @@
model Template {
id String @id @default(uuid())
language String
deliveryCharge String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
address Address?
banner Banner?
ingredient Ingredient?
instruction Instruction?
faq FAQ?
tips Tips?
priceCombo PriceCombo?
product Product?
}
// banner model
model Banner {
id String @id @default(uuid())
isVisible Boolean
bannerTitle String
bannerDesc String
bannerImage String
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
}
// address model
model Address {
id String @id @default(uuid())
isVisible Boolean
phoneNumber String
shopLocation String
shopEmail String
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
}
//ingredient model
model Ingredient {
id String @id @default(uuid())
isVisible Boolean
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
options ingredientOptions[]
}
//ingredient options model
model ingredientOptions {
id String @id @default(uuid())
ingrImg String
ingrTitle String
ingrDes String
ingredientId String
ingredient Ingredient @relation(fields: [ingredientId], references: [id])
}
//instruction model & instruction options
model Instruction {
id String @id @default(uuid())
isVisible Boolean
instBanner String
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
options InstructionOptions[]
}
model InstructionOptions {
id String @id @default(uuid())
hint String
detail String
instructionId String
instruction Instruction @relation(fields: [instructionId], references: [id])
}
//FAQ & FAQOptions model
model FAQ {
id String @id @default(uuid())
isVisible Boolean
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
options FAQOptions[]
}
model FAQOptions {
id String @id @default(uuid())
index Int
text String
faqId String
faq FAQ @relation(fields: [faqId], references: [id])
}
//Tips & Tips options model
model Tips {
id String @id @default(uuid())
isVisible Boolean
tipsBanner String
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
options TipsOption[]
}
model TipsOption {
id String @id @default(uuid())
index Int
text String
tipsId String
tips Tips @relation(fields: [tipsId], references: [id])
}
//PriceCombo & PriceComboOptions model
model PriceCombo {
id String @id @default(uuid())
isVisible Boolean
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
options PriceOption[]
}
model PriceOption {
id String @id @default(uuid())
quantity String
price String
comboId String
combo PriceCombo @relation(fields: [comboId], references: [id])
}
//product model
model Product {
id String @id @default(uuid())
isVisible Boolean
price String
discount Int
productName String
templateId String @unique
template Template @relation(fields: [templateId], references: [id])
}
+7
View File
@@ -0,0 +1,7 @@
model Users {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
+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"],
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,
); );
+131 -56
View File
@@ -1,61 +1,124 @@
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,
},
select: {
id: true,
customerName: true,
productQuantity: true,
productPrice: true,
status: true,
createdAt: true,
},
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 +130,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)
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 +144,23 @@ 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;
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 +168,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;
+13 -4
View File
@@ -1,11 +1,20 @@
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
const result = await prisma.plan.findMany(); const result = await prisma.plan.findMany({
select:{
planName: true,
price: true,
planType: true,
planDesc: true,
planFeatures: true,
}
});
return result; return result;
}; };
@@ -22,7 +31,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();
+3 -3
View File
@@ -1,7 +1,7 @@
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;
+1 -1
View File
@@ -13,7 +13,7 @@ export const profileSwaggerDocs = {
data: { data: {
type: "object", type: "object",
properties: { properties: {
fullName: { type: "string" }, shopName: { type: "string" },
}, },
}, },
file: { file: {
+10 -1
View File
@@ -1,6 +1,15 @@
import { z } from "zod"; import { z } from "zod";
const update_profile = z.object({ const update_profile = z.object({
fullName: z.string().optional(), shopName: z.string().optional(),
shopAddress: z.string().optional(),
shopPhone: z.string().optional(),
shopLocation: z.string().optional(),
shopImage: z.string().optional(),
shopMapLocation: z.string().optional(),
contactNumber: z.string().optional(),
shopCategory: z.string().optional(),
}); });
export const profile_validations = { export const profile_validations = {
@@ -0,0 +1,138 @@
export const staticticsSwaggerDocs = {
"/api/statictics": {
post: {
tags: ["statictics"],
summary: "Create new statictics",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({}), // put your request body
},
},
},
responses: {
201: { description: "statictics created successfully" },
500: { description: "Validation error or internal server error" },
},
},
get: {
tags: ["statictics"],
summary: "Get all statictics",
description: "",
parameters: [
{
name: "page",
in: "query",
required: false,
schema: { type: "number" },
},
{
name: "limit",
in: "query",
required: false,
schema: { type: "number" },
},
],
responses: {
200: { description: "statictics fetched successfully" },
401: { description: "unauthorized" },
},
},
},
"/api/statictics/seller": {
get: {
tags: ["statistics"],
summary: "Get seller statistics",
description: "Fetch seller dashboard stats (7d, 30d, all)",
parameters: [
{
name: "range",
in: "query",
required: false,
schema: {
type: "string",
enum: ["7d", "30d", "all"],
default: "7d",
},
description: "Time range for statistics",
},
],
responses: {
200: {
description: "Statistics fetched successfully",
},
401: {
description: "Unauthorized",
},
},
},
},
"/api/statictics/{id}": {
get: {
tags: ["statictics"],
summary: "Get single statictics",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "statictics fetched successfully" },
401: { description: "unauthorized" },
},
},
patch: {
tags: ["statictics"],
summary: "Update statictics",
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: "statictics updated successfully" },
500: { description: "Validation error or internal server error" },
},
},
delete: {
tags: ["statictics"],
summary: "Delete statictics",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "statictics delete successfully" },
401: { description: "unauthorized" },
},
},
},
};
@@ -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,
};
@@ -0,0 +1,66 @@
import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response.js";
import { template_service } from "./template.service.js";
const get_all_template = catchAsync(async (req, res) => {
const result = await template_service.get_all_template_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "All template fetched successfully.",
data: result,
meta: {},
});
});
const get_single_template = catchAsync(async (req, res) => {
const result = await template_service.get_single_template_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "Single template fetched successfully.",
data: result,
meta: {},
});
});
const create_template = catchAsync(async (req, res) => {
const result = await template_service.create_template_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "template created successfully.",
data: result,
meta: {},
});
});
const update_template = catchAsync(async (req, res) => {
const result = await template_service.update_template_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "template updated successfully.",
data: result,
meta: {},
});
});
const delete_template = catchAsync(async (req, res) => {
const result = await template_service.delete_template_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "template deleted successfully.",
data: result,
meta: {},
});
});
export const template_controller = {
get_all_template,
get_single_template,
create_template,
update_template,
delete_template,
};
@@ -0,0 +1,22 @@
import { Router } from "express";
import RequestValidator from "../../middlewares/request_validator.js";
import { template_controller } from "./template.controller.js";
import { template_validations } from "./template.validation.js";
const router = Router();
router.get("/", template_controller.get_all_template);
router.post(
"/",
RequestValidator(template_validations.create_template),
template_controller.create_template,
);
router.get("/:id", template_controller.get_single_template);
router.patch(
"/:id",
RequestValidator(template_validations.update_template),
template_controller.update_template,
);
router.delete("/:id", template_controller.delete_template);
export default router;
@@ -0,0 +1,143 @@
import { Request } from "express";
import { prisma } from "../../lib/prisma.js";
const get_all_template_from_db = async (req: Request) => {
// define your own login here
const result = await prisma.template.findMany({});
return result;
};
const get_single_template_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.template.findUnique({
where: { id },
select: {
id: true,
language: true,
deliveryCharge: true,
banner: true,
address: true,
ingredient: true,
instruction: true,
faq: true,
tips: true,
priceCombo: true,
product: true,
},
});
return result;
};
const create_template_into_db = async (req: Request) => {
// define your own login here
const payload = req.body;
console.log(payload);
const result = await prisma.template.create({
data: {
language: payload.language,
deliveryCharge: payload.deliveryCharge,
banner: {
create: payload.banner,
},
address: {
create: payload.address,
},
ingredient: {
create: {
isVisible: payload.ingredient.isVisible,
options: {
create: payload.ingredient.options,
},
},
},
instruction: {
create: {
isVisible: payload.instruction.isVisible,
instBanner: payload.instruction.instBanner,
options: {
create: payload.instruction.options,
},
},
},
faq: {
create: {
isVisible: payload.faq.isVisible,
options: {
create: payload.faq.options,
},
},
},
tips: {
create: {
isVisible: payload.tips.isVisible,
tipsBanner: payload.tips.tipsBanner,
options: {
create: payload.tips.options,
},
},
},
priceCombo: {
create: {
isVisible: payload.priceCombo.isVisible,
options: {
create: payload.priceCombo.options,
},
},
},
product: {
create: {
isVisible: payload.product.isVisible,
price: payload.product.price,
discount: payload.product.discount,
productName: payload.product.productName,
},
},
},
include: {
banner: true,
address: true,
ingredient: { include: { options: true } },
instruction: { include: { options: true } },
faq: { include: { options: true } },
tips: { include: { options: true } },
priceCombo: { include: { options: true } },
product: true,
},
});
return result;
};
const update_template_into_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.template.update({
where: { id },
data: req.body,
});
return result;
};
const delete_template_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.template.delete({ where: { id } });
return result;
};
export const template_service = {
get_all_template_from_db,
get_single_template_from_db,
create_template_into_db,
update_template_into_db,
delete_template_from_db,
};
@@ -0,0 +1,197 @@
export const templateSwaggerDocs = {
"/api/template": {
post: {
tags: ["template"],
summary: "Create new template",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({
language: "en",
deliveryCharge: "60",
banner: {
isVisible: true,
bannerTitle: "Delicious Homemade Food",
bannerDesc:
"Fresh, healthy and tasty meals delivered to your door.",
bannerImage: "https://example.com/banner.jpg",
},
address: {
isVisible: true,
phoneNumber: "+8801712345678",
shopLocation: "Dhaka, Bangladesh",
shopEmail: "foodshop@example.com",
},
ingredient: {
isVisible: true,
options: [
{
ingrImg: "https://example.com/tomato.jpg",
ingrTitle: "Tomato",
ingrDes: "Fresh organic tomatoes",
},
{
ingrImg: "https://example.com/chicken.jpg",
ingrTitle: "Chicken",
ingrDes: "Premium quality chicken",
},
],
},
instruction: {
isVisible: true,
instBanner: "https://example.com/instruction-banner.jpg",
options: [
{
hint: "Step 1",
detail: "Wash all ingredients properly",
},
{
hint: "Step 2",
detail: "Cook on medium heat for 20 minutes",
},
],
},
faq: {
isVisible: true,
options: [
{
index: 1,
text: "Is the food fresh?",
},
{
index: 2,
text: "Do you offer home delivery?",
},
],
},
tips: {
isVisible: true,
tipsBanner: "https://example.com/tips-banner.jpg",
options: [
{
index: 1,
text: "Use fresh ingredients for best taste",
},
{
index: 2,
text: "Serve hot for better flavor",
},
],
},
priceCombo: {
isVisible: true,
options: [
{
quantity: "1",
price: "120",
},
{
quantity: "2",
price: "220",
},
],
},
product: {
isVisible: true,
price: "120",
discount: 10,
productName: "Chicken Burger",
},
}), // put your request body
},
},
},
responses: {
201: { description: "template created successfully" },
500: { description: "Validation error or internal server error" },
},
},
get: {
tags: ["template"],
summary: "Get all template",
description: "",
parameters: [
{
name: "page",
in: "query",
required: false,
schema: { type: "number" },
},
{
name: "limit",
in: "query",
required: false,
schema: { type: "number" },
},
],
responses: {
200: { description: "template fetched successfully" },
401: { description: "unauthorized" },
},
},
},
"/api/template/{id}": {
get: {
tags: ["template"],
summary: "Get single template",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "template fetched successfully" },
401: { description: "unauthorized" },
},
},
patch: {
tags: ["template"],
summary: "Update template",
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: "template updated successfully" },
500: { description: "Validation error or internal server error" },
},
},
delete: {
tags: ["template"],
summary: "Delete template",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "template delete successfully" },
401: { description: "unauthorized" },
},
},
},
};
@@ -0,0 +1,87 @@
import { z } from "zod";
// Template Options that we need to use for validation.
// Ingredient Option
const ingredientOptionSchema = z.object({
ingrImg: z.string().url().or(z.string()),
ingrTitle: z.string().min(1),
ingrDes: z.string().min(1),
});
// Instruction Option
const instructionOptionSchema = z.object({
hint: z.string().min(1),
detail: z.string().min(1),
});
// FAQ Option
const faqOptionSchema = z.object({
index: z.number(),
text: z.string().min(1),
});
// Tips Option
const tipsOptionSchema = z.object({
index: z.number(),
text: z.string().min(1),
});
// Price Option
const priceOptionSchema = z.object({
quantity: z.string().min(1),
price: z.string().min(1),
});
// Create the main template schema validation
const create_template = z.object({
language: z.string().min(1),
deliveryCharge: z.string().min(1),
banner: z.object({
isVisible: z.boolean(),
bannerTitle: z.string().min(1),
bannerDesc: z.string().min(1),
bannerImage: z.string(),
}),
address: z.object({
isVisible: z.boolean(),
phoneNumber: z.string().min(1),
shopLocation: z.string().min(1),
shopEmail: z.string().email(),
}),
ingredient: z.object({
isVisible: z.boolean(),
options: z.array(ingredientOptionSchema).min(1),
}),
instruction: z.object({
isVisible: z.boolean(),
instBanner: z.string(),
options: z.array(instructionOptionSchema).min(1),
}),
faq: z.object({
isVisible: z.boolean(),
options: z.array(faqOptionSchema).min(1),
}),
tips: z.object({
isVisible: z.boolean(),
tipsBanner: z.string(),
options: z.array(tipsOptionSchema).min(1),
}),
priceCombo: z.object({
isVisible: z.boolean(),
options: z.array(priceOptionSchema).min(1),
}),
product: z.object({
isVisible: z.boolean(),
price: z.string().min(1),
discount: z.number().min(0),
productName: z.string().min(1),
}),
});
const update_template = z.object({});
export const template_validations = {
create_template,
update_template,
};
+69
View File
@@ -0,0 +1,69 @@
import catchAsync from "../../utils/catch_async.js";
import manageResponse from "../../utils/manage_response.js";
import { users_service } from "./users.service.js";
const get_all_users = catchAsync(async (req, res) => {
const result = await users_service.get_all_users_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "All users fetched successfully.",
data: result,
meta: {},
});
});
const get_single_users = catchAsync(async (req, res) => {
const result = await users_service.get_single_users_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "Single users fetched successfully.",
data: result,
meta: {},
});
});
const create_users = catchAsync(async (req, res) => {
const result = await users_service.create_users_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "users created successfully.",
data: result,
meta: {},
});
});
const update_users = catchAsync(async (req, res) => {
const result = await users_service.update_users_into_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "users updated successfully.",
data: result,
meta: {},
});
});
const delete_users = catchAsync(async (req, res) => {
const result = await users_service.delete_users_from_db(req);
manageResponse(res, {
success: true,
statusCode: 200,
message: "users deleted successfully.",
data: result,
meta: {},
});
});
export const users_controller = {
get_all_users,
get_single_users,
create_users,
update_users,
delete_users,
};
+24
View File
@@ -0,0 +1,24 @@
import { Router } from "express";
import { users_controller } from "./users.controller.js";
import { users_validations } from "./users.validation.js";
import RequestValidator from "../../middlewares/request_validator.js";
const router = Router();
router.get("/", users_controller.get_all_users);
router.post(
"/",
RequestValidator(users_validations.create_users),
users_controller.create_users,
);
router.get("/:id", users_controller.get_single_users);
router.patch(
"/:id",
RequestValidator(users_validations.update_users),
users_controller.update_users,
);
router.delete("/:id", users_controller.delete_users);
export default router;
+70
View File
@@ -0,0 +1,70 @@
import { Request } from "express";
import { prisma } from "../../lib/prisma.js";
import paginationHelper from "../../utils/pagination_helper.js";
const get_all_users_from_db = async (req: Request) => {
const { page, skip, limit } = paginationHelper(req.query);
const search = req.query.search as string;
const andCondition = {} as any;
if (search) {
andCondition.shopName = {
contains: search,
mode: "insensitive",
};
}
// define your own login here
const result = await prisma.profile.findMany({
take: limit,
skip,
where: andCondition,
select: {
account: {
select: {
isSubscribe: true,
email: true,
},
},
shopName: true,
id: true,
status: true,
},
});
const usersCount = await prisma.profile.count();
return { result, usersCount, page, limit, skip };
};
const get_single_users_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.profile.findUnique({ where: { id } });
return result;
};
const create_users_into_db = async (req: Request) => {
// define your own login here
const result = await prisma.account.create({ data: req.body });
return result;
};
const update_users_into_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.account.update({ where: { id }, data: req.body });
return result;
};
const delete_users_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params as { id: string };
const result = await prisma.account.delete({ where: { id } });
return result;
};
export const users_service = {
get_all_users_from_db,
get_single_users_from_db,
create_users_into_db,
update_users_into_db,
delete_users_from_db,
};
+112
View File
@@ -0,0 +1,112 @@
export const usersSwaggerDocs = {
"/api/users": {
post: {
tags: ["users"],
summary: "Create new users",
description: "",
requestBody: {
required: true,
content: {
"application/json": {
example: JSON.stringify({}), // put your request body
},
},
},
responses: {
201: { description: "users created successfully" },
500: { description: "Validation error or internal server error" },
},
},
get: {
tags: ["users"],
summary: "Get all users",
description: "",
parameters: [
{
name: "page",
in: "query",
required: false,
schema: { type: "number" },
},
{
name: "limit",
in: "query",
required: false,
schema: { type: "number" },
},
{
name: "search",
in: "query",
required: false,
schema: { type: "string" },
},
],
responses: {
200: { description: "users fetched successfully" },
401: { description: "unauthorized" },
},
},
},
"/api/users/{id}": {
get: {
tags: ["users"],
summary: "Get single users",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "users fetched successfully" },
401: { description: "unauthorized" },
},
},
patch: {
tags: ["users"],
summary: "Update users",
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: "users updated successfully" },
500: { description: "Validation error or internal server error" },
},
},
delete: {
tags: ["users"],
summary: "Delete users",
description: "",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
},
],
responses: {
200: { description: "users delete successfully" },
401: { description: "unauthorized" },
},
},
},
};
+10
View File
@@ -0,0 +1,10 @@
import { z } from "zod";
const create_users = z.object({});
const update_users = z.object({});
export const users_validations = {
create_users,
update_users,
};
+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;
+20 -12
View File
@@ -1,15 +1,23 @@
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(); import templateRoute from "./app/modules/template/template.route";
import usersRoute from "./app/modules/users/users.route";
const appRouter = Router();
const moduleRoutes = [ const moduleRoutes = [
{ path: "/users", route: usersRoute },
{ path: "/template", route: templateRoute },
{ 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 {
+14 -8
View File
@@ -1,10 +1,13 @@
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";
import { templateSwaggerDocs } from "./app/modules/template/template.swagger";
import { usersSwaggerDocs } from "./app/modules/users/users.swagger";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@@ -19,14 +22,17 @@ export const swaggerOptions = {
}, },
paths: { paths: {
...accountSwaggerDocs, ...accountSwaggerDocs,
...profileSwaggerDocs,
...planSwaggerDocs, ...planSwaggerDocs,
...orderSwaggerDocs, ...orderSwaggerDocs,
...profileSwaggerDocs,
...supportSwaggerDocs,
...templateSwaggerDocs,
...usersSwaggerDocs,
}, },
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",