Merge pull request 'Fix/missing environment variable type#7' (#15) from fix/missing-environment-variable-type#7 into dev

Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
2026-06-18 05:05:50 +00:00
28 changed files with 1459 additions and 178 deletions
@@ -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';
+8 -3
View File
@@ -1,3 +1,10 @@
enum ShopStatus {
ACTIVE
SUSPENDED
DELETED
INACTIVE
}
model Profile {
id String @id @default(uuid())
accountId String @unique
@@ -8,7 +15,5 @@ model Profile {
shopAddress String?
shopMapLocation String?
shopCategory String?
status ShopStatus @default(ACTIVE)
}
+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
}
+51 -98
View File
@@ -1,76 +1,73 @@
# ⚡ Express Server CLI
# QuickLaunch Server
[![npm version](https://img.shields.io/npm/v/exp-node-server.svg)](https://www.npmjs.com/package/exp-node-server)
[![npm downloads](https://img.shields.io/npm/dw/exp-node-server.svg)](https://www.npmjs.com/package/exp-node-server)
[![npm total downloads](https://img.shields.io/npm/dt/exp-node-server.svg)](https://www.npmjs.com/package/exp-node-server)
[![Made with TypeScript](https://img.shields.io/badge/Made%20with-TypeScript-blue.svg)](https://www.typescriptlang.org/)
[![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
### This server is a nodejs server with express framework. It provides a REST API for quicklunch app. It is written in typescript and uses prisma for database.It is deployed on docker and is managed by docker compose.
---
### Features
## 🚀 Overview
- Authentication
- Authorization
- Database Management
- REST API
- Testing
- Deployment
A powerful `Express + TypeScript CLI` that instantly generates scalable `backend` modules with `Mongoose `/ `Prisma`, `Zod` `validation`, and `Swagger` documentation.
### Technologies
This tool helps developers quickly scaffold `clean`, `modular` `Express` APIs with minimal setup.
- Nodejs
- Express
- Typescript
- Prisma
- Docker
- Docker Compose
- Bullmq
-Nodemailer
## ✨ Features
### Deployment
- ⚡ Generate complete `Express` + `TypeScript` modules
- 🧩 Built-in `Mongoose` / `Prisma` support
- 📘 Adds Swagger documentation automatically
- 📘 Automatic `Swagger` documentation
- 🔐 `Zod` validation ready
- 🏗️ `Modular` clean architecture
- 🚀 One command project setup
- 🔄 Add modules anytime
- 📦 Zero boilerplate setup
- Docker
- Docker Compose
## 📦 Quick Start
### Authentication
Run directly using npx (recommended):
- JWT
```Bash
npx exp-node-server -c my-api
```
### Installation
This will:
- git clone git@codelab.techzaa.tech:summer2026/quicklanch-server.git
- cd quicklunch-server
- npm install
- npm run dev
- Create an Express starter project
- Install dependencies
- Prepare the project for development
### Environment variables are stored in .env file. They are:
Set up or add the following environment variables in .env file to run the server. You will copy and paste them from the .env.example file by adding Database URL,APP USER EMAIL, APP PASSWORD, CLOUD NAME, CLOUD API KEY, CLOUD API SECRET and REDIS URL :
- PORT = 5000
- DATABASE_URL
-ACCESS_TOKEN=8b8ba26578276a8bf9d0599f8c0ec0d2d69e0aec9171e989a314c36db0b330a23fd0365a6c9b1059406856046e28dc0e13c9d6a165e2936e6614aa2d1862af68
- ACCESS_EXPIRES=24h
- RESET_SECRET=8b8ba26578276a8bf9d0599f8c0ec0d2d69e0aec9171e989a314c36db0b330a23fd0365a6c9b1059406856046e28dc0e13c9d6a165e2936e6614aa2d1862af68
- RESET_EXPIRES=5m
- VERIFIED_TOKEN=8b8ba26578276a8bf9d0599f8c0ec0d2d69e0aec9171e989a314c36db0b330a23fd0365a6c9b1059406856046e28dc0e13c9d6a165e2936e6614aa2d1862af68
- FRONT_END_URL=http://localhost:5173/
- APP_USER_EMAIL
- APP_PASSWORD
- CLOUD_NAME
- CLOUD_API_KEY
- CLOUD_API_SECRET
- REDIS_URL
## 🧩 Generate a Module
### API Documentation
Inside your project run:
- API documentation is available at http://localhost:5000/api-docs
### API Creating
If you need to create an new api you will only run simple command the command is
```bash
npx express-server-cli -g <module-name>
npx exp-node-server -g API_NAME (Your api name or folder name)
```
Example: `npx express-server-cli -g order`
Output:
```Bash
✔ order.interface.ts created
✔ order.schema.ts created
✔ order.validation.ts created
✔ order.route.ts created
✔ order.controller.ts created
✔ order.service.ts created
✔ order.swagger.ts created
🔗 Route registered in routes.ts
📘 Swagger docs registered
### API Structure
```
## 📁 Generated Module Structure
Each module follows a clean structure:
```Bash
src/app/modules/<module-name>/
├── <module>.interface.ts
├── <module>.schema.ts
@@ -80,47 +77,3 @@ src/app/modules/<module-name>/
├── <module>.service.ts
└── <module>.swagger.ts
```
## ⚙️ Run the Project
```bash
npm run dev
```
Swagger docs available at:
```bash
http://localhost:5000/docs
```
## 🧠 Tech Stack
- Express.js — Backend framework
- TypeScript — Strongly typed JavaScript
- Mongoose — MongoDB ODM
- Prisma — PostgreSQL ORM
- Zod — Runtime schema validation
- JWT — Authentication
- Swagger — API documentation
## 👨‍💻 Author
### Abumahid
GitHub
https://github.com/dev-abumahid
npm
https://www.npmjs.com/~dev_abumahid
Portfolio
https://abumahid.me
LinkedIn
https://linkedin.com/in/md-abu-mahid-islam
## ⭐ Support
If you find this project helpful, consider giving it a ⭐ on `GitHub`.
It helps the project grow and reach more developers.
+29 -17
View File
@@ -1,27 +1,39 @@
import "dotenv/config";
function getEnv(key: string): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}
export const configs = {
port: process.env.PORT,
env: process.env.NODE_ENV,
db_url: process.env.DATABASE_URL,
port: getEnv("PORT"),
env: getEnv("NODE_ENV"),
db_url: getEnv("DATABASE_URL"),
jwt: {
access_token: process.env.ACCESS_TOKEN,
refresh_token: process.env.REFRESH_TOKEN,
access_expires: process.env.ACCESS_EXPIRES,
refresh_expires: process.env.REFRESH_EXPIRES,
reset_secret: process.env.RESET_SECRET,
reset_expires: process.env.RESET_EXPIRES,
front_end_url: process.env.FRONT_END_URL,
verified_token: process.env.VERIFIED_TOKEN,
access_token: getEnv("ACCESS_TOKEN"),
access_expires: getEnv("ACCESS_EXPIRES"),
reset_secret: getEnv("RESET_SECRET"),
reset_expires: getEnv("RESET_EXPIRES"),
front_end_url: getEnv("FRONT_END_URL"),
verified_token: getEnv("VERIFIED_TOKEN"),
},
email: {
app_email: process.env.APP_USER_EMAIL,
app_password: process.env.APP_PASSWORD,
app_email: getEnv("APP_USER_EMAIL"),
app_password: getEnv("APP_PASSWORD"),
},
cloudinary: {
cloud_name: process.env.CLOUD_NAME,
cloud_api_key: process.env.CLOUD_API_KEY,
cloud_api_secret: process.env.CLOUD_API_SECRET,
cloud_name: getEnv("CLOUD_NAME"),
cloud_api_key: getEnv("CLOUD_API_KEY"),
cloud_api_secret: getEnv("CLOUD_API_SECRET"),
},
redis_url: process.env.REDIS_URL,
redis_url: getEnv("REDIS_URL"),
};
+19 -40
View File
@@ -19,54 +19,30 @@ const get_all_order_from_db = async (req: Request) => {
const status = (req.query.status as string) || undefined;
const { page, limit, skip, sortBy, sortOrder } = paginationHelper(req.query);
const andCondition = [] as any[];
const andCondition = {} as any;
if (search) {
andCondition.push({
OR: [
{
productName: {
andCondition.productName = {
contains: search,
mode: "insensitive",
},
},
],
});
};
}
if (customerName) {
andCondition.push({
OR: [
{
customerName: {
andCondition.customerName = {
contains: customerName,
mode: "insensitive",
},
},
],
});
};
}
if (productName) {
andCondition.push({
OR: [
{
productName: {
andCondition.productName = {
contains: productName,
mode: "insensitive",
},
},
],
});
};
}
if (status) {
andCondition.push({
OR: [
{
status: {
andCondition.status = {
contains: status,
mode: "insensitive",
},
},
],
});
};
}
// for date filter
@@ -82,16 +58,20 @@ const get_all_order_from_db = async (req: Request) => {
dateFilter.lte = end;
}
if (Object.keys(dateFilter).length > 0) {
andCondition.push({
createdAt: dateFilter,
});
andCondition.createdAt = dateFilter;
}
const getAllOrders = await prisma.order.findMany({
take: limit,
skip,
where: {
AND: andCondition,
where: andCondition,
select: {
id: true,
customerName: true,
productQuantity: true,
productPrice: true,
status: true,
createdAt: true,
},
orderBy: {
[sortBy as string]: sortOrder,
@@ -122,7 +102,7 @@ const get_single_order_from_db = async (req: Request) => {
const create_order_into_db = async (req: Request) => {
const payload = req?.body;
console.log(payload);
payload.status = "INITIATED";
payload.paymentType = "COD";
@@ -145,7 +125,6 @@ const create_order_into_db = async (req: Request) => {
const update_order_into_db = async (req: Request) => {
// 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);
}
+1 -1
View File
@@ -121,7 +121,7 @@ export const orderSwaggerDocs = {
patch: {
tags: ["order"],
summary: "Update order -(Admin route)",
description: "",
description: "The status can be updated to any of the following values : INITIATED,CONFIRMED,ONGOING,DELIVERED,CANCELLED",
parameters: [
{
name: "id",
+1 -1
View File
@@ -13,7 +13,7 @@ const create_order = z.object({
customerNote: z.string().optional()
});
const update_order = z.object({
status: z.string().optional()
status: z.enum(["INITIATED","CONFIRMED","ONGOING","DELIVERED","CANCELLED"]).optional()
});
export const order_validations = {
+10 -1
View File
@@ -5,7 +5,16 @@ import { AppError } from "../../utils/app_error.js";
const get_all_plan_from_db = async (req: Request) => {
// 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;
};
@@ -7,7 +7,6 @@ const update_profile_into_db = async (req: Request) => {
const user = req?.user as JwtPayloadType;
const payload = req?.body;
const file = req?.file;
console.log(payload);
// check file and upload to cloud
if (file) {
const cloudRes = await uploadCloud(file);
+1 -1
View File
@@ -13,7 +13,7 @@ export const profileSwaggerDocs = {
data: {
type: "object",
properties: {
fullName: { type: "string" },
shopName: { type: "string" },
},
},
file: {
+10 -1
View File
@@ -1,6 +1,15 @@
import { z } from "zod";
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 = {
@@ -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,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,
};
+4
View File
@@ -4,10 +4,14 @@ import orderRoute from "./app/modules/order/order.route.js";
import planRoute from "./app/modules/plan/plan.route.js";
import profileRoute from "./app/modules/profile/profile.route.js";
import supportRoute from "./app/modules/support/support.route.js";
import templateRoute from "./app/modules/template/template.route.js";
import usersRoute from "./app/modules/users/users.route.js";
const appRouter = Router();
const moduleRoutes = [
{ path: "/users", route: usersRoute },
{ path: "/template", route: templateRoute },
{ path: "/order", route: orderRoute },
{ path: "/support", route: supportRoute },
{ path: "/plan", route: planRoute },
+4
View File
@@ -6,6 +6,8 @@ import { orderSwaggerDocs } from "./app/modules/order/order.swagger.js";
import { planSwaggerDocs } from "./app/modules/plan/plan.swagger.js";
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.js";
import { usersSwaggerDocs } from "./app/modules/users/users.swagger.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -24,6 +26,8 @@ export const swaggerOptions = {
...orderSwaggerDocs,
...profileSwaggerDocs,
...supportSwaggerDocs,
...templateSwaggerDocs,
...usersSwaggerDocs,
},
servers:
configs.env === "production"