From 815e24158a443fdfb2e6fdf5c339af1c508e78c1 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Sun, 1 Mar 2026 12:22:23 +0700 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20db:=20update=20hero?= =?UTF-8?q?=20banner=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9050711..b3bcf48 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,22 @@ model EmailSystemHistory { @@map("email_system_histories") } +model HeroBanner { + id String @id @db.Uuid + 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]) +} + model SystemPreference { id String @id @db.Uuid key String @db.VarChar(225) From 90655dcf92110fbc87e238b329a29df46bc86b03 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Sun, 1 Mar 2026 12:28:16 +0700 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=9A=9A=20chore:=20rename=20hero=20ban?= =?UTF-8?q?ner=20table=20in=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration.sql | 20 ++++++++++++ .../migration.sql | 32 +++++++++++++++++++ prisma/schema.prisma | 1 + 3 files changed, 53 insertions(+) create mode 100644 prisma/migrations/20260301052432_add_hero_banner/migration.sql create mode 100644 prisma/migrations/20260301052641_rename_hero_banner/migration.sql 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/schema.prisma b/prisma/schema.prisma index b3bcf48..d907814 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -568,6 +568,7 @@ model HeroBanner { updatedAt DateTime @default(now()) @updatedAt creatorId String @db.Uuid createdBy User @relation("UserHeroBanner", fields: [creatorId], references: [id]) + @@map("hero_banner") } model SystemPreference { From 960493f414f1a2a2df506d0f69ed0829af514dce Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Mon, 2 Mar 2026 22:09:33 +0700 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20internal=20endpoi?= =?UTF-8?q?nt=20to=20create=20banner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/dbml/schema.dbml | 19 +++++++++++++++ prisma/schema.prisma | 2 +- .../createHeroBanner.controller.ts | 24 +++++++++++++++++++ src/modules/internal/index.ts | 4 +++- .../insertHeroBanner.repository.ts | 21 ++++++++++++++++ .../services/http/createHeroBanner.service.ts | 13 ++++++++++ 6 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/modules/internal/controllers/createHeroBanner.controller.ts create mode 100644 src/modules/internal/repositories/insertHeroBanner.repository.ts create mode 100644 src/modules/internal/services/http/createHeroBanner.service.ts diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index 524cbfe..5cede02 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,22 @@ Table email_system_histories { updatedAt DateTime [default: `now()`, not null] } +Table hero_banner { + id String [pk] + 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 +778,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/schema.prisma b/prisma/schema.prisma index d907814..5e1d9ba 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -564,7 +564,7 @@ model HeroBanner { imageUrl String? @db.Text startDate DateTime endDate DateTime - createdAt DateTime @default(now()) + createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt creatorId String @db.Uuid createdBy User @relation("UserHeroBanner", fields: [creatorId], references: [id]) 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); + } +}; From 6ffa087e91309c680bdac16e30b054bf3999ea45 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Mon, 2 Mar 2026 22:32:05 +0700 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20endpoint=20to=20g?= =?UTF-8?q?et=20active=20banners?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../getActiveHeroBanner.controller.ts | 18 ++++++++++++++++++ src/modules/heroBanner/index.ts | 7 +++++++ .../GET/findAllActiveHeroBanner.repository.ts | 19 +++++++++++++++++++ .../services/getActiveHeroBanner.service.ts | 10 ++++++++++ 4 files changed, 54 insertions(+) create mode 100644 src/modules/heroBanner/controllers/getActiveHeroBanner.controller.ts create mode 100644 src/modules/heroBanner/index.ts create mode 100644 src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts create mode 100644 src/modules/heroBanner/services/getActiveHeroBanner.service.ts 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..6b899d9 --- /dev/null +++ b/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts @@ -0,0 +1,19 @@ +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(), + }, + }, + }); + } 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); + } +}; From 01ad69a4b075db87b868be92e140bcc229998988 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Mon, 2 Mar 2026 23:07:30 +0700 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=91=94=20feat:=20add=20banner=20prior?= =?UTF-8?q?ity=20ordering=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/dbml/schema.dbml | 1 + .../migration.sql | 11 +++++++++++ .../migration.sql | 16 ++++++++++++++++ prisma/schema.prisma | 1 + .../GET/findAllActiveHeroBanner.repository.ts | 8 ++++++++ 5 files changed, 37 insertions(+) create mode 100644 prisma/migrations/20260302155255_add_order_in_hero_banner/migration.sql create mode 100644 prisma/migrations/20260302160211_rename_order_column/migration.sql diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index 5cede02..6996dca 100644 --- a/prisma/dbml/schema.dbml +++ b/prisma/dbml/schema.dbml @@ -495,6 +495,7 @@ Table email_system_histories { Table hero_banner { id String [pk] + orderPriority Int [unique] isClickable Boolean [not null, default: false] title String description String 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 5e1d9ba..4c9b2be 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -556,6 +556,7 @@ model EmailSystemHistory { model HeroBanner { id String @id @db.Uuid + orderPriority Int? @unique isClickable Boolean @default(false) title String? @db.VarChar(225) description String? @db.Text diff --git a/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts b/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts index 6b899d9..e5cc8b1 100644 --- a/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts +++ b/src/modules/heroBanner/repositories/GET/findAllActiveHeroBanner.repository.ts @@ -12,6 +12,14 @@ export const findAllActiveHeroBannerRepository = async () => { gte: new Date(), }, }, + orderBy: [ + { + orderPriority: "asc", + }, + { + startDate: "asc", + }, + ], }); } catch (error) { throw new AppError(500, "Failed to fetch active hero banners", error);