From 47d30d96ebca33d0dec63f37f4e4caa64ecfd22b Mon Sep 17 00:00:00 2001 From: Md Sharafat Hassain Binoy Date: Mon, 27 Apr 2026 00:03:06 +0600 Subject: [PATCH] Template API: create database schema --- .../migration.sql | 207 ++++++++++++++++++ prisma/schema/template.prisma | 139 ++++++++++++ .../modules/template/template.controller.ts | 66 ++++++ src/app/modules/template/template.route.ts | 22 ++ src/app/modules/template/template.service.ts | 46 ++++ src/app/modules/template/template.swagger.ts | 106 +++++++++ .../modules/template/template.validation.ts | 9 + src/routes.ts | 2 + src/swaggerOptions.ts | 2 + 9 files changed, 599 insertions(+) create mode 100644 prisma/migrations/20260426174845_add_template_data_tables/migration.sql create mode 100644 prisma/schema/template.prisma create mode 100644 src/app/modules/template/template.controller.ts create mode 100644 src/app/modules/template/template.route.ts create mode 100644 src/app/modules/template/template.service.ts create mode 100644 src/app/modules/template/template.swagger.ts create mode 100644 src/app/modules/template/template.validation.ts diff --git a/prisma/migrations/20260426174845_add_template_data_tables/migration.sql b/prisma/migrations/20260426174845_add_template_data_tables/migration.sql new file mode 100644 index 0000000..c768a98 --- /dev/null +++ b/prisma/migrations/20260426174845_add_template_data_tables/migration.sql @@ -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; diff --git a/prisma/schema/template.prisma b/prisma/schema/template.prisma new file mode 100644 index 0000000..027c5e7 --- /dev/null +++ b/prisma/schema/template.prisma @@ -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]) +} diff --git a/src/app/modules/template/template.controller.ts b/src/app/modules/template/template.controller.ts new file mode 100644 index 0000000..b89d917 --- /dev/null +++ b/src/app/modules/template/template.controller.ts @@ -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, +}; diff --git a/src/app/modules/template/template.route.ts b/src/app/modules/template/template.route.ts new file mode 100644 index 0000000..352af4e --- /dev/null +++ b/src/app/modules/template/template.route.ts @@ -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; diff --git a/src/app/modules/template/template.service.ts b/src/app/modules/template/template.service.ts new file mode 100644 index 0000000..3654585 --- /dev/null +++ b/src/app/modules/template/template.service.ts @@ -0,0 +1,46 @@ +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; + const result = await prisma.template.findUnique({ where: { id } }); + return result; +}; + +const create_template_into_db = async (req: Request) => { + // define your own login here + const result = await prisma.template.create({ data: req.body }); + return result; +}; + +const update_template_into_db = async (req: Request) => { + // define your own login here + const { id } = req.params; + 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; + 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, +}; diff --git a/src/app/modules/template/template.swagger.ts b/src/app/modules/template/template.swagger.ts new file mode 100644 index 0000000..d315cf2 --- /dev/null +++ b/src/app/modules/template/template.swagger.ts @@ -0,0 +1,106 @@ +export const templateSwaggerDocs = { + "/api/template": { + post: { + tags: ["template"], + summary: "Create new template", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({}), // 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" }, + }, + }, + }, +}; diff --git a/src/app/modules/template/template.validation.ts b/src/app/modules/template/template.validation.ts new file mode 100644 index 0000000..53ba562 --- /dev/null +++ b/src/app/modules/template/template.validation.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +const create_template = z.object({}); +const update_template = z.object({}); + +export const template_validations = { + create_template, + update_template, +}; diff --git a/src/routes.ts b/src/routes.ts index a7ac1a2..faf8148 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -4,10 +4,12 @@ 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"; const appRouter = Router(); const moduleRoutes = [ + { path: "/template", route: templateRoute }, { path: "/order", route: orderRoute }, { path: "/support", route: supportRoute }, { path: "/plan", route: planRoute }, diff --git a/src/swaggerOptions.ts b/src/swaggerOptions.ts index 93859d5..3aa0273 100644 --- a/src/swaggerOptions.ts +++ b/src/swaggerOptions.ts @@ -6,6 +6,7 @@ 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"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -24,6 +25,7 @@ export const swaggerOptions = { ...orderSwaggerDocs, ...profileSwaggerDocs, ...supportSwaggerDocs, + ...templateSwaggerDocs, }, servers: configs.env === "production"