Compare commits

..

5 Commits

Author SHA1 Message Date
56c921e800 👔 feat: add collection module 2026-03-27 23:42:26 +07:00
e798338107 🗃️ db: update schema for new collection 2026-03-27 22:56:18 +07:00
dade012888 Merge pull request 'hotfix/payload-banner' (#28) from hotfix/payload-banner into main
All checks were successful
Sync to GitHub / sync (push) Successful in 8s
Reviewed-on: #28
2026-03-25 17:57:49 +07:00
4001aec6ef ♻️ refactor: refine payload before sending to frontend
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 47s
2026-03-25 17:55:48 +07:00
dd70f9f9d4 ♻️ refactor: restructure banner select payload 2026-03-25 17:49:16 +07:00
7 changed files with 107 additions and 12 deletions

View File

@ -0,0 +1,14 @@
/*
Warnings:
- You are about to alter the column `name` on the `collections` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(115)`.
- A unique constraint covering the columns `[slug,ownerId]` on the table `collections` will be added. If there are existing duplicate values, this will fail.
- Added the required column `slug` to the `collections` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "collections" ADD COLUMN "slug" VARCHAR(115) NOT NULL,
ALTER COLUMN "name" SET DATA TYPE VARCHAR(115);
-- CreateIndex
CREATE UNIQUE INDEX "collections_slug_ownerId_key" ON "collections"("slug", "ownerId");

View File

@ -418,7 +418,8 @@ model UserLog {
model Collection {
id String @id @db.Uuid
name String @db.VarChar(255)
name String @db.VarChar(115)
slug String @db.VarChar(115)
medias Media[] @relation("MediaCollections")
owner User @relation("UserCollections", fields: [ownerId], references: [id])
ownerId String @db.Uuid
@ -429,6 +430,8 @@ model Collection {
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@unique([slug, ownerId])
@@map("collections")
}

View File

@ -0,0 +1,10 @@
import { Context, Static } from "elysia";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { addItemToCollectionSchema } from "../schemas/addItemToCollection.schema";
export const addItemToCollectionController = async (ctx: {
set: Context["set"];
headers: Static<typeof addItemToCollectionSchema.headers>;
}) => {
return returnWriteResponse(ctx.set, 200, "Item added to collection successfully" + ctx.headers.cookie);
};

View File

@ -0,0 +1,9 @@
import Elysia from "elysia";
import { addItemToCollectionController } from "./controllers/addItemToCollection.controller";
import { addItemToCollectionSchema } from "./schemas/addItemToCollection.schema";
export const collectionModule = new Elysia({ prefix: "/collections", tags: ["Collections"] }).post(
"/:name",
addItemToCollectionController,
addItemToCollectionSchema,
);

View File

@ -0,0 +1,35 @@
import { t } from "elysia";
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
export const addItemToCollectionSchema = {
headers: t.Object({
cookie: t.String({ description: "Authentication token in cookie format, e.g., auth_token=your_jwt_token;" }),
}),
params: t.Object({
name: t.String({ description: "Name of the collection to which the item will be added" }),
}),
body: t.Object({
itemId: t.String({ description: "ID of the item to be added to the collection", examples: ["12345"] }),
}),
detail: {
summary: "Add an item to a collection",
description: "Adds a specified item to a collection identified by its name.",
responses: {
200: {
description: "The item was successfully added to the collection.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
status: { type: "number", example: 200 },
message: { type: "string", example: "Item added to collection successfully" },
},
},
},
},
},
},
},
} satisfies AppRouteSchema;

View File

@ -20,6 +20,25 @@ export const findAllActiveHeroBannerRepository = async () => {
startDate: "asc",
},
],
select: {
orderPriority: true,
imageUrl: true,
media: {
select: {
id: true,
title: true,
slug: true,
pictureLarge: true,
synopsis: true,
genres: {
select: {
slug: true,
name: true,
},
},
},
},
},
});
} catch (error) {
throw new AppError(500, "Failed to fetch active hero banners", error);

View File

@ -8,26 +8,31 @@ import { findAllActiveHeroBannerRepository } from "../repositories/GET/findAllAc
export const getActiveHeroBannerService = async () => {
try {
// Check if Hero Banner is enabled in system preferences
const isHeroBannerEnabled = await findSystemPreferenceService(
"HERO_BANNER_ENABLED",
"boolean",
);
if (!isHeroBannerEnabled)
throw new AppError(403, "Hero Banner is disabled");
const isHeroBannerEnabled = await findSystemPreferenceService("HERO_BANNER_ENABLED", "boolean");
if (!isHeroBannerEnabled) throw new AppError(403, "Hero Banner is disabled");
// Try to get active banners from Redis cache
const cachedBanners = await redis.get(
`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`,
);
const cachedBanners = await redis.get(`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`);
if (cachedBanners) return JSON.parse(cachedBanners);
// If not in cache, fetch from database and cache the result
const activeBanners = await findAllActiveHeroBannerRepository();
const constructedBanners = activeBanners.map((banner) => ({
id: banner.media.id,
title: banner.media.title,
slug: banner.media.slug,
imageUrl: banner.imageUrl || banner.media.pictureLarge,
synopsis: banner.media.synopsis,
genres: banner.media.genres.map((genre) => ({
slug: genre.slug,
name: genre.name,
})),
}));
await redis.set(
`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`,
JSON.stringify(activeBanners),
JSON.stringify(constructedBanners),
);
return activeBanners;
return constructedBanners;
} catch (error) {
ErrorForwarder(error);
}