diff --git a/prisma/migrations/20260406143513_add_plan_schema/migration.sql b/prisma/migrations/20260406143513_add_plan_schema/migration.sql new file mode 100644 index 0000000..d9f3a98 --- /dev/null +++ b/prisma/migrations/20260406143513_add_plan_schema/migration.sql @@ -0,0 +1,29 @@ +/* + Warnings: + + - Added the required column `subscriptionId` to the `Account` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "PType" AS ENUM ('FREE', 'STANDARD', 'PRO'); + +-- AlterTable +ALTER TABLE "Account" ADD COLUMN "isSubscribe" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "subscriptionId" TEXT NOT NULL; + +-- CreateTable +CREATE TABLE "Plan" ( + "id" TEXT NOT NULL, + "planName" TEXT NOT NULL, + "price" INTEGER NOT NULL, + "planType" "PType" NOT NULL, + "planDesc" TEXT NOT NULL, + "planFeatures" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Plan_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "Plan"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20260406153500_init/migration.sql b/prisma/migrations/20260406153500_init/migration.sql new file mode 100644 index 0000000..03dd053 --- /dev/null +++ b/prisma/migrations/20260406153500_init/migration.sql @@ -0,0 +1,8 @@ +-- DropForeignKey +ALTER TABLE "Account" DROP CONSTRAINT "Account_subscriptionId_fkey"; + +-- AlterTable +ALTER TABLE "Account" ALTER COLUMN "subscriptionId" DROP NOT NULL; + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "Plan"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema/account.schema.prisma b/prisma/schema/account.schema.prisma index 675bb28..fbd9f48 100644 --- a/prisma/schema/account.schema.prisma +++ b/prisma/schema/account.schema.prisma @@ -12,6 +12,9 @@ model Account { lastOtpSendingTime DateTime? isDeleted Boolean @default(false) isAccountVerified Boolean @default(false) + isSubscribe Boolean @default(false) + subscriptionId String? + plan Plan? @relation(fields: [subscriptionId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) diff --git a/prisma/schema/plan.prisma b/prisma/schema/plan.prisma new file mode 100644 index 0000000..bf94e0f --- /dev/null +++ b/prisma/schema/plan.prisma @@ -0,0 +1,17 @@ +enum PType { + FREE + STANDARD + PRO +} + +model Plan { + id String @id @default(uuid()) + planName String + price Int + planType PType + planDesc String + planFeatures Json + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + account Account[] +} diff --git a/src/app/modules/plan/plan.controller.ts b/src/app/modules/plan/plan.controller.ts new file mode 100644 index 0000000..06f8efd --- /dev/null +++ b/src/app/modules/plan/plan.controller.ts @@ -0,0 +1,68 @@ + +import catchAsync from "../../utils/catch_async"; +import manageResponse from "../../utils/manage_response"; +import { plan_service } from "./plan.service"; + +const get_all_plan = catchAsync(async (req, res) => { + const result = await plan_service.get_all_plan_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "All plan fetched successfully.", + data: result, + meta: {}, + }); +}); + +const get_single_plan = catchAsync(async (req, res) => { + const result = await plan_service.get_single_plan_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "Single plan fetched successfully.", + data: result, + meta: {}, + }); +}); + +const create_plan = catchAsync(async (req, res) => { + const result = await plan_service.create_plan_into_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "plan created successfully.", + data: result, + meta: {}, + }); +}); + +const update_plan = catchAsync(async (req, res) => { + const result = await plan_service.update_plan_into_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "plan updated successfully.", + data: result, + meta: {}, + }); +}); + +const delete_plan = catchAsync(async (req, res) => { + const result = await plan_service.delete_plan_from_db(req); + manageResponse(res, { + success: true, + statusCode: 200, + message: "plan deleted successfully.", + data: result, + meta: {}, + }); +}); + +export const plan_controller = { + get_all_plan, + get_single_plan, + create_plan, + update_plan, + delete_plan, +}; + \ No newline at end of file diff --git a/src/app/modules/plan/plan.route.ts b/src/app/modules/plan/plan.route.ts new file mode 100644 index 0000000..b05a285 --- /dev/null +++ b/src/app/modules/plan/plan.route.ts @@ -0,0 +1,24 @@ + + import { Router } from "express"; +import RequestValidator from "../../middlewares/request_validator"; +import { plan_controller } from "./plan.controller"; +import { plan_validations } from "./plan.validation"; + +const router = Router(); + +router.get("/", plan_controller.get_all_plan); +router.post( + "/", + RequestValidator(plan_validations.create_plan), + plan_controller.create_plan, +); +router.get("/:id", plan_controller.get_single_plan); +router.patch( + "/:id", + RequestValidator(plan_validations.update_plan), + plan_controller.update_plan, +); +router.delete("/:id", plan_controller.delete_plan); + +export default router; + \ No newline at end of file diff --git a/src/app/modules/plan/plan.service.ts b/src/app/modules/plan/plan.service.ts new file mode 100644 index 0000000..434e90e --- /dev/null +++ b/src/app/modules/plan/plan.service.ts @@ -0,0 +1,45 @@ + +import { Request } from "express"; +import { prisma } from "../../lib/prisma"; + +const get_all_plan_from_db = async (req: Request) => { + // define your own login here + const result = await prisma.plan.findMany(); + return result; +}; + +const get_single_plan_from_db = async (req: Request) => { + // define your own login here + const { id } = req.params ; + const result = await prisma.plan.findUnique({ where: { id } }); + return result; +}; + +const create_plan_into_db = async (req: Request) => { + // define your own login here + console.log(req.body) + const result = await prisma.plan.create({ data: req.body }); + return result; +}; + +const update_plan_into_db = async (req: Request) => { + // define your own login here + const { id } = req.params; + const result = await prisma.plan.update({ where: { id }, data: req.body }); + return result; +}; + +const delete_plan_from_db = async (req: Request) => { + // define your own login here + const { id } = req.params; + const result = await prisma.plan.delete({ where: { id } }); + return result; +}; + +export const plan_service = { + get_all_plan_from_db, + get_single_plan_from_db, + create_plan_into_db, + update_plan_into_db, + delete_plan_from_db, +}; diff --git a/src/app/modules/plan/plan.swagger.ts b/src/app/modules/plan/plan.swagger.ts new file mode 100644 index 0000000..82ce586 --- /dev/null +++ b/src/app/modules/plan/plan.swagger.ts @@ -0,0 +1,123 @@ + +export const planSwaggerDocs = { + "/api/plan": { + post: { + tags: ["plan"], + summary: "Create new plan", + description: "", + requestBody: { + required: true, + content: { + "application/json": { + example: JSON.stringify({ + + "planName": "PRO Plan", + "price": 12, + "planType": "PRO", + "planDesc": "The plan is only for pro users", + "planFeatures": { + "storage": "10GB", + "projects": 5, + "support": "Email Support" + } + + + + }), // put your request body + }, + }, + }, + responses: { + 201: { description: "plan created successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + get: { + tags: ["plan"], + summary: "Get all plan", + description: "", + parameters: [ + { + name: "page", + in: "query", + required: false, + schema: { type: "number" }, + }, + { + name: "limit", + in: "query", + required: false, + schema: { type: "number" }, + }, + ], + responses: { + 200: { description: "plan fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, + + "/api/plan/{id}": { + get: { + tags: ["plan"], + summary: "Get single plan", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "plan fetched successfully" }, + 401: { description: "unauthorized" }, + }, + }, + patch: { + tags: ["plan"], + summary: "Update plan", + 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: "plan updated successfully" }, + 500: { description: "Validation error or internal server error" }, + }, + }, + delete: { + tags: ["plan"], + summary: "Delete plan", + description: "", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + 200: { description: "plan delete successfully" }, + 401: { description: "unauthorized" }, + }, + }, + }, +}; + + diff --git a/src/app/modules/plan/plan.validation.ts b/src/app/modules/plan/plan.validation.ts new file mode 100644 index 0000000..7ee87b4 --- /dev/null +++ b/src/app/modules/plan/plan.validation.ts @@ -0,0 +1,23 @@ + +import { z } from "zod"; + +const create_plan = z.object({ + planName: z.string(), + price: z.number(), + planType: z.enum(["FREE", "STANDARD", "PRO"]), + planDesc: z.string(), + planFeatures: z.union([ + z.string(), + z.number(), + z.boolean(), + z.null(), + z.array(z.any()), + z.record(z.string(), z.any()) + ]) +}); +const update_plan = z.object({}); + +export const plan_validations = { + create_plan, + update_plan, +}; diff --git a/src/routes.ts b/src/routes.ts index 6327d7a..c7b2768 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,10 +1,12 @@ import { Router } from "express"; import accountRouter from "./app/modules/account/account.route"; import profileRoute from "./app/modules/profile/profile.route"; +import planRoute from "./app/modules/plan/plan.route"; const appRouter = Router(); const moduleRoutes = [ + { path: "/plan", route: planRoute }, { path: "/profile", route: profileRoute },{ path: "/auth", route: accountRouter }]; moduleRoutes.forEach((route) => appRouter.use(route.path, route.route)); diff --git a/src/server.ts b/src/server.ts index b265e0d..193e962 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ import app from "./app"; import { configs } from "./app/configs/index"; import { prisma } from "./app/lib/prisma"; -import "./app/queues/worker"; +// import "./app/queues/worker"; async function main() { try { diff --git a/src/swaggerOptions.ts b/src/swaggerOptions.ts index c5f49f4..a787bda 100644 --- a/src/swaggerOptions.ts +++ b/src/swaggerOptions.ts @@ -3,6 +3,7 @@ import path from "path"; import { configs } from "./app/configs"; import { accountSwaggerDocs } from "./app/modules/account/account.swagger"; import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger"; +import { planSwaggerDocs } from "./app/modules/plan/plan.swagger"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -18,6 +19,7 @@ export const swaggerOptions = { paths: { ...accountSwaggerDocs, ...profileSwaggerDocs, + ...planSwaggerDocs, }, servers: configs.env === "production"