feat/hero-banner #21

Merged
vivy-agent merged 5 commits from feat/hero-banner into main 2026-03-02 23:24:20 +07:00
14 changed files with 241 additions and 1 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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");

View File

@ -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");

View File

@ -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)

View File

@ -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);
}
};

View File

@ -0,0 +1,7 @@
import Elysia from "elysia";
import { getActiveHeroBannerController } from "./controllers/getActiveHeroBanner.controller";
export const heroBannerModule = new Elysia({ prefix: "/hero-banner" }).get(
"/",
getActiveHeroBannerController,
);

View File

@ -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);
}
};

View File

@ -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);
}
};

View File

@ -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);
}
};

View File

@ -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);

View File

@ -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<Prisma.HeroBannerCreateInput, "id" | "createdBy">,
) => {
try {
return await prisma.heroBanner.create({
data: {
id: generateUUIDv7(),
creatorId: SystemAccountId,
...payload,
},
});
} catch (error) {
throw new AppError(500, "Failed to insert hero banner", error);
}
};

View File

@ -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);
}
};