diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index 524cbfe..6996dca 100644 --- a/prisma/dbml/schema.dbml +++ b/prisma/dbml/schema.dbml @@ -257,6 +257,7 @@ Table users { create_languages languages [not null] user_create_email email_system_accounts [not null] user_emails email_system_histories [not null] + user_hero_banner hero_banner [not null] sys_notifications system_notifications [not null] sys_logs system_logs [not null] } @@ -492,6 +493,23 @@ Table email_system_histories { updatedAt DateTime [default: `now()`, not null] } +Table hero_banner { + id String [pk] + orderPriority Int [unique] + isClickable Boolean [not null, default: false] + title String + description String + buttonContent String + buttonLink String + imageUrl String + startDate DateTime [not null] + endDate DateTime [not null] + createdAt DateTime [default: `now()`, not null] + updatedAt DateTime [default: `now()`, not null] + creatorId String [not null] + createdBy users [not null] +} + Table system_preferences { id String [pk] key String [not null] @@ -761,6 +779,8 @@ Ref: email_system_accounts.createdBy > users.id Ref: email_system_histories.userRelated > users.id +Ref: hero_banner.creatorId > users.id + Ref: system_notifications.createdBy > users.id Ref: system_logs.relatedUser > users.id \ No newline at end of file diff --git a/prisma/migrations/20260301052432_add_hero_banner/migration.sql b/prisma/migrations/20260301052432_add_hero_banner/migration.sql new file mode 100644 index 0000000..64de72c --- /dev/null +++ b/prisma/migrations/20260301052432_add_hero_banner/migration.sql @@ -0,0 +1,20 @@ +-- CreateTable +CREATE TABLE "HeroBanner" ( + "id" UUID NOT NULL, + "isClickable" BOOLEAN NOT NULL DEFAULT false, + "title" VARCHAR(225), + "description" TEXT, + "buttonContent" VARCHAR(100), + "buttonLink" TEXT, + "imageUrl" TEXT, + "startDate" TIMESTAMP(3) NOT NULL, + "endDate" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "creatorId" UUID NOT NULL, + + CONSTRAINT "HeroBanner_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "HeroBanner" ADD CONSTRAINT "HeroBanner_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20260301052641_rename_hero_banner/migration.sql b/prisma/migrations/20260301052641_rename_hero_banner/migration.sql new file mode 100644 index 0000000..8fb2907 --- /dev/null +++ b/prisma/migrations/20260301052641_rename_hero_banner/migration.sql @@ -0,0 +1,32 @@ +/* + Warnings: + + - You are about to drop the `HeroBanner` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "HeroBanner" DROP CONSTRAINT "HeroBanner_creatorId_fkey"; + +-- DropTable +DROP TABLE "HeroBanner"; + +-- CreateTable +CREATE TABLE "hero_banner" ( + "id" UUID NOT NULL, + "isClickable" BOOLEAN NOT NULL DEFAULT false, + "title" VARCHAR(225), + "description" TEXT, + "buttonContent" VARCHAR(100), + "buttonLink" TEXT, + "imageUrl" TEXT, + "startDate" TIMESTAMP(3) NOT NULL, + "endDate" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "creatorId" UUID NOT NULL, + + CONSTRAINT "hero_banner_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "hero_banner" ADD CONSTRAINT "hero_banner_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20260302155255_add_order_in_hero_banner/migration.sql b/prisma/migrations/20260302155255_add_order_in_hero_banner/migration.sql new file mode 100644 index 0000000..0d0e645 --- /dev/null +++ b/prisma/migrations/20260302155255_add_order_in_hero_banner/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - A unique constraint covering the columns `[order]` on the table `hero_banner` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "hero_banner" ADD COLUMN "order" INTEGER; + +-- CreateIndex +CREATE UNIQUE INDEX "hero_banner_order_key" ON "hero_banner"("order"); diff --git a/prisma/migrations/20260302160211_rename_order_column/migration.sql b/prisma/migrations/20260302160211_rename_order_column/migration.sql new file mode 100644 index 0000000..532f842 --- /dev/null +++ b/prisma/migrations/20260302160211_rename_order_column/migration.sql @@ -0,0 +1,16 @@ +/* + Warnings: + + - You are about to drop the column `order` on the `hero_banner` table. All the data in the column will be lost. + - A unique constraint covering the columns `[orderPriority]` on the table `hero_banner` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropIndex +DROP INDEX "hero_banner_order_key"; + +-- AlterTable +ALTER TABLE "hero_banner" DROP COLUMN "order", +ADD COLUMN "orderPriority" INTEGER; + +-- CreateIndex +CREATE UNIQUE INDEX "hero_banner_orderPriority_key" ON "hero_banner"("orderPriority"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9050711..4c9b2be 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -296,6 +296,7 @@ model User { create_languages Language[] @relation("UserCreateLanguages") user_create_email EmailSystemAccount[] @relation("UserCreateSystemAccount") user_emails EmailSystemHistory[] @relation("UserEmails") + user_hero_banner HeroBanner[] @relation("UserHeroBanner") sys_notifications SystemNotification[] @relation("UserCreatorSystemNotifications") sys_logs SystemLog[] @relation("UserSystemLogs") @@map("users") @@ -553,6 +554,24 @@ model EmailSystemHistory { @@map("email_system_histories") } +model HeroBanner { + id String @id @db.Uuid + orderPriority Int? @unique + isClickable Boolean @default(false) + title String? @db.VarChar(225) + description String? @db.Text + buttonContent String? @db.VarChar(100) + buttonLink String? @db.Text + imageUrl String? @db.Text + startDate DateTime + endDate DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + creatorId String @db.Uuid + createdBy User @relation("UserHeroBanner", fields: [creatorId], references: [id]) + @@map("hero_banner") +} + model SystemPreference { id String @id @db.Uuid key String @db.VarChar(225) diff --git a/src/modules/heroBanner/controllers/getActiveHeroBanner.controller.ts b/src/modules/heroBanner/controllers/getActiveHeroBanner.controller.ts new file mode 100644 index 0000000..65c454d --- /dev/null +++ b/src/modules/heroBanner/controllers/getActiveHeroBanner.controller.ts @@ -0,0 +1,18 @@ +import { Context } from "elysia"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { returnReadResponse } from "../../../helpers/callback/httpResponse"; +import { getActiveHeroBannerService } from "../services/getActiveHeroBanner.service"; + +export const getActiveHeroBannerController = async (ctx: Context) => { + try { + const response = await getActiveHeroBannerService(); + return returnReadResponse( + ctx.set, + 200, + "Active hero banners fetched successfully", + response, + ); + } catch (error) { + return mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/heroBanner/index.ts b/src/modules/heroBanner/index.ts new file mode 100644 index 0000000..5ee94c1 --- /dev/null +++ b/src/modules/heroBanner/index.ts @@ -0,0 +1,7 @@ +import Elysia from "elysia"; +import { getActiveHeroBannerController } from "./controllers/getActiveHeroBanner.controller"; + +export const heroBannerModule = new Elysia({ prefix: "/hero-banner" }).get( + "/", + getActiveHeroBannerController, +); diff --git a/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts b/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts new file mode 100644 index 0000000..e5cc8b1 --- /dev/null +++ b/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts @@ -0,0 +1,27 @@ +import { AppError } from "../../../../helpers/error/instances/app"; +import { prisma } from "../../../../utils/databases/prisma/connection"; + +export const findAllActiveHeroBannerRepository = async () => { + try { + return await prisma.heroBanner.findMany({ + where: { + startDate: { + lte: new Date(), + }, + endDate: { + gte: new Date(), + }, + }, + orderBy: [ + { + orderPriority: "asc", + }, + { + startDate: "asc", + }, + ], + }); + } catch (error) { + throw new AppError(500, "Failed to fetch active hero banners", error); + } +}; diff --git a/src/modules/heroBanner/services/getActiveHeroBanner.service.ts b/src/modules/heroBanner/services/getActiveHeroBanner.service.ts new file mode 100644 index 0000000..b6093c9 --- /dev/null +++ b/src/modules/heroBanner/services/getActiveHeroBanner.service.ts @@ -0,0 +1,10 @@ +import { ErrorForwarder } from "../../../helpers/error/instances/forwarder"; +import { findAllActiveHeroBannerRepository } from "../repositories/GET/findAllActiveHeroBanner.repository"; + +export const getActiveHeroBannerService = async () => { + try { + return await findAllActiveHeroBannerRepository(); + } catch (error) { + ErrorForwarder(error); + } +}; diff --git a/src/modules/internal/controllers/createHeroBanner.controller.ts b/src/modules/internal/controllers/createHeroBanner.controller.ts new file mode 100644 index 0000000..9e8b18f --- /dev/null +++ b/src/modules/internal/controllers/createHeroBanner.controller.ts @@ -0,0 +1,24 @@ +import { Context } from "elysia"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { createHeroBannerService } from "../services/http/createHeroBanner.service"; + +export interface CreateHeroBannerRequestBody { + isClickable?: boolean; + title?: string; + description?: string; + buttonContent?: string; + buttonLink?: string; + imageUrl?: string; + startDate: string; + endDate: string; +} + +export const createHeroBannerController = async ( + ctx: Context & { body: CreateHeroBannerRequestBody }, +) => { + try { + return await createHeroBannerService(ctx.body); + } catch (error) { + return mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/internal/index.ts b/src/modules/internal/index.ts index 4e9b58c..e7291ed 100644 --- a/src/modules/internal/index.ts +++ b/src/modules/internal/index.ts @@ -5,6 +5,7 @@ import { createVideoServiceInternalController } from "./controllers/createVideoS import { bulkInsertVideoController } from "./controllers/bulkInsertVideo.controller"; import { updateAllEpisodeThumbnailController } from "./controllers/updateAllEpisodeThumbnail.controller"; import { purgeUnusedSessionController } from "./controllers/purgeUnusedSession.controller"; +import { createHeroBannerController } from "./controllers/createHeroBanner.controller"; export const internalModule = new Elysia({ prefix: "/internal" }) .post("/media/bulk-insert", bulkInsertMediaController) @@ -12,4 +13,5 @@ export const internalModule = new Elysia({ prefix: "/internal" }) .put("/episode/update-thumbnails", updateAllEpisodeThumbnailController) .post("/video/bulk-insert", bulkInsertVideoController) .post("/video-service", createVideoServiceInternalController) - .post("/user-session/purge-unused", purgeUnusedSessionController); + .post("/user-session/purge-unused", purgeUnusedSessionController) + .post("/hero-banner", createHeroBannerController); diff --git a/src/modules/internal/repositories/insertHeroBanner.repository.ts b/src/modules/internal/repositories/insertHeroBanner.repository.ts new file mode 100644 index 0000000..c6b7d35 --- /dev/null +++ b/src/modules/internal/repositories/insertHeroBanner.repository.ts @@ -0,0 +1,21 @@ +import { Prisma } from "@prisma/client"; +import { AppError } from "../../../helpers/error/instances/app"; +import { prisma } from "../../../utils/databases/prisma/connection"; +import { generateUUIDv7 } from "../../../helpers/databases/uuidv7"; +import { SystemAccountId } from "../../../config/account/system"; + +export const insertHeroBannerRepository = async ( + payload: Omit, +) => { + try { + return await prisma.heroBanner.create({ + data: { + id: generateUUIDv7(), + creatorId: SystemAccountId, + ...payload, + }, + }); + } catch (error) { + throw new AppError(500, "Failed to insert hero banner", error); + } +}; diff --git a/src/modules/internal/services/http/createHeroBanner.service.ts b/src/modules/internal/services/http/createHeroBanner.service.ts new file mode 100644 index 0000000..d1a8f2a --- /dev/null +++ b/src/modules/internal/services/http/createHeroBanner.service.ts @@ -0,0 +1,13 @@ +import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; +import { CreateHeroBannerRequestBody } from "../../controllers/createHeroBanner.controller"; +import { insertHeroBannerRepository } from "../../repositories/insertHeroBanner.repository"; + +export const createHeroBannerService = async ( + payload: CreateHeroBannerRequestBody, +) => { + try { + return await insertHeroBannerRepository(payload); + } catch (error) { + ErrorForwarder(error); + } +};