Compare commits
1 Commits
main
...
a6200605f8
| Author | SHA1 | Date | |
|---|---|---|---|
| a6200605f8 |
@ -28,11 +28,10 @@ Table medias {
|
||||
deletedAt DateTime
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
bannerPromotion hero_banner [not null]
|
||||
logs media_logs [not null]
|
||||
episodes episodes [not null]
|
||||
collections collections [not null]
|
||||
reviews movie_reviews [not null]
|
||||
inCollections CollectionMedia [not null]
|
||||
}
|
||||
|
||||
Table media_logs {
|
||||
@ -369,7 +368,7 @@ Table user_logs {
|
||||
Table collections {
|
||||
id String [pk]
|
||||
name String [not null]
|
||||
slug String [not null]
|
||||
medias medias [not null]
|
||||
owner users [not null]
|
||||
ownerId String [not null]
|
||||
accessStatus AccessStatus [not null, default: 'private']
|
||||
@ -379,24 +378,6 @@ Table collections {
|
||||
deletedAt DateTime
|
||||
createdAt DateTime [default: `now()`, not null]
|
||||
updatedAt DateTime [default: `now()`, not null]
|
||||
media_saved CollectionMedia [not null]
|
||||
|
||||
indexes {
|
||||
(slug, ownerId) [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Table CollectionMedia {
|
||||
id String [pk]
|
||||
collection collections [not null]
|
||||
collectionId String [not null]
|
||||
media medias [not null]
|
||||
mediaId String [not null]
|
||||
savedAt DateTime [default: `now()`, not null]
|
||||
|
||||
indexes {
|
||||
(collectionId, mediaId) [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Table watch_histories {
|
||||
@ -515,8 +496,12 @@ Table email_system_histories {
|
||||
Table hero_banner {
|
||||
id String [pk]
|
||||
orderPriority Int [unique]
|
||||
mediaId String [not null]
|
||||
media medias [not null]
|
||||
isClickable Boolean [not null, default: false]
|
||||
title String
|
||||
tags String[] [not null]
|
||||
description String
|
||||
buttonContent String
|
||||
buttonLink String
|
||||
imageUrl String
|
||||
startDate DateTime [not null]
|
||||
endDate DateTime [not null]
|
||||
@ -575,9 +560,9 @@ Table MediaCharacters {
|
||||
mediasId String [ref: > medias.id]
|
||||
}
|
||||
|
||||
Table CollectionMedia {
|
||||
incollectionsId String [ref: > CollectionMedia.id]
|
||||
media_savedId String [ref: > CollectionMedia.id]
|
||||
Table MediaCollections {
|
||||
collectionsId String [ref: > collections.id]
|
||||
mediasId String [ref: > medias.id]
|
||||
}
|
||||
|
||||
Table UserFavoriteGenres {
|
||||
@ -765,10 +750,6 @@ Ref: user_logs.sessionId > user_sessions.id
|
||||
|
||||
Ref: collections.ownerId > users.id
|
||||
|
||||
Ref: CollectionMedia.collectionId > collections.id
|
||||
|
||||
Ref: CollectionMedia.mediaId > medias.id
|
||||
|
||||
Ref: watch_histories.id > episodes.id
|
||||
|
||||
Ref: watch_histories.userId > users.id
|
||||
@ -799,8 +780,6 @@ Ref: email_system_accounts.createdBy > users.id
|
||||
|
||||
Ref: email_system_histories.userRelated > users.id
|
||||
|
||||
Ref: hero_banner.mediaId > medias.id
|
||||
|
||||
Ref: hero_banner.creatorId > users.id
|
||||
|
||||
Ref: system_notifications.createdBy > users.id
|
||||
|
||||
@ -7,9 +7,6 @@ CREATE TYPE "MediaType" AS ENUM ('TV', 'ONA', 'OVA', 'Movie', 'Special', 'Music'
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Country" AS ENUM ('Japanese', 'English', 'Indonesia', 'Korea');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Season" AS ENUM ('Winter', 'Spring', 'Summer', 'Fall');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CharacterRole" AS ENUM ('Main', 'Supporting');
|
||||
|
||||
@ -75,8 +72,6 @@ CREATE TABLE "medias" (
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"season" "Season",
|
||||
"yearReleased" SMALLINT NOT NULL,
|
||||
|
||||
CONSTRAINT "medias_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@ -211,8 +206,7 @@ CREATE TABLE "videos" (
|
||||
"id" UUID NOT NULL,
|
||||
"episodeId" UUID NOT NULL,
|
||||
"serviceId" UUID NOT NULL,
|
||||
"videoCode" VARCHAR(255) NOT NULL,
|
||||
"thumbnailCode" TEXT,
|
||||
"code" VARCHAR(255) NOT NULL,
|
||||
"pendingUpload" BOOLEAN NOT NULL DEFAULT true,
|
||||
"uploadedBy" UUID NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
@ -372,7 +366,7 @@ CREATE TABLE "user_logs" (
|
||||
-- CreateTable
|
||||
CREATE TABLE "collections" (
|
||||
"id" UUID NOT NULL,
|
||||
"name" VARCHAR(115) NOT NULL,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"ownerId" UUID NOT NULL,
|
||||
"accessStatus" "AccessStatus" NOT NULL DEFAULT 'private',
|
||||
"password" VARCHAR(255),
|
||||
@ -380,21 +374,10 @@ CREATE TABLE "collections" (
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"slug" VARCHAR(115) NOT NULL,
|
||||
|
||||
CONSTRAINT "collections_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CollectionMedia" (
|
||||
"id" UUID NOT NULL,
|
||||
"collectionId" UUID NOT NULL,
|
||||
"mediaId" UUID NOT NULL,
|
||||
"savedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "CollectionMedia_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "watch_histories" (
|
||||
"id" UUID NOT NULL,
|
||||
@ -514,21 +497,6 @@ CREATE TABLE "email_system_histories" (
|
||||
CONSTRAINT "email_system_histories_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "hero_banner" (
|
||||
"id" UUID NOT NULL,
|
||||
"orderPriority" INTEGER,
|
||||
"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,
|
||||
"mediaId" UUID NOT NULL,
|
||||
|
||||
CONSTRAINT "hero_banner_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "system_preferences" (
|
||||
"id" UUID NOT NULL,
|
||||
@ -602,6 +570,14 @@ CREATE TABLE "_MediaCharacters" (
|
||||
CONSTRAINT "_MediaCharacters_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_MediaCollections" (
|
||||
"A" UUID NOT NULL,
|
||||
"B" UUID NOT NULL,
|
||||
|
||||
CONSTRAINT "_MediaCollections_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_UserSelectedSharingCollention" (
|
||||
"A" UUID NOT NULL,
|
||||
@ -653,7 +629,7 @@ CREATE UNIQUE INDEX "lang_va_char_language_vaId_charId_key" ON "lang_va_char"("l
|
||||
CREATE UNIQUE INDEX "episodes_mediaId_episode_key" ON "episodes"("mediaId", "episode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "videos_serviceId_videoCode_key" ON "videos"("serviceId", "videoCode");
|
||||
CREATE UNIQUE INDEX "videos_serviceId_code_key" ON "videos"("serviceId", "code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "video_services_name_key" ON "video_services"("name");
|
||||
@ -676,12 +652,6 @@ CREATE UNIQUE INDEX "user_roles_name_key" ON "user_roles"("name");
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_sessions_userId_isAuthenticated_deletedAt_idx" ON "user_sessions"("userId", "isAuthenticated", "deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "collections_slug_ownerId_key" ON "collections"("slug", "ownerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CollectionMedia_collectionId_mediaId_key" ON "CollectionMedia"("collectionId", "mediaId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "languages_code_key" ON "languages"("code");
|
||||
|
||||
@ -694,12 +664,6 @@ CREATE UNIQUE INDEX "email_system_accounts_email_key" ON "email_system_accounts"
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "email_system_accounts_username_key" ON "email_system_accounts"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "hero_banner_orderPriority_key" ON "hero_banner"("orderPriority");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "system_preferences_key_key" ON "system_preferences"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_MediaStudios_B_index" ON "_MediaStudios"("B");
|
||||
|
||||
@ -712,21 +676,24 @@ CREATE INDEX "_UserFavoriteGenres_B_index" ON "_UserFavoriteGenres"("B");
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_MediaCharacters_B_index" ON "_MediaCharacters"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_MediaCollections_B_index" ON "_MediaCollections"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_UserSelectedSharingCollention_B_index" ON "_UserSelectedSharingCollention"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "medias" ADD CONSTRAINT "medias_uploadedBy_fkey" FOREIGN KEY ("uploadedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "media_logs" ADD CONSTRAINT "media_logs_proposedBy_fkey" FOREIGN KEY ("proposedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "media_logs" ADD CONSTRAINT "media_logs_approvedBy_fkey" FOREIGN KEY ("approvedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "media_logs" ADD CONSTRAINT "media_logs_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "medias"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "media_logs" ADD CONSTRAINT "media_logs_proposedBy_fkey" FOREIGN KEY ("proposedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "genres" ADD CONSTRAINT "genres_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
@ -739,15 +706,15 @@ ALTER TABLE "characters" ADD CONSTRAINT "characters_creatorId_fkey" FOREIGN KEY
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "voice_actors" ADD CONSTRAINT "voice_actors_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "lang_va_char" ADD CONSTRAINT "lang_va_char_vaId_fkey" FOREIGN KEY ("vaId") REFERENCES "voice_actors"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "lang_va_char" ADD CONSTRAINT "lang_va_char_charId_fkey" FOREIGN KEY ("charId") REFERENCES "characters"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "lang_va_char" ADD CONSTRAINT "lang_va_char_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "lang_va_char" ADD CONSTRAINT "lang_va_char_vaId_fkey" FOREIGN KEY ("vaId") REFERENCES "voice_actors"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "episodes" ADD CONSTRAINT "episodes_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "medias"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
@ -755,13 +722,13 @@ ALTER TABLE "episodes" ADD CONSTRAINT "episodes_mediaId_fkey" FOREIGN KEY ("medi
|
||||
ALTER TABLE "episodes" ADD CONSTRAINT "episodes_uploadedBy_fkey" FOREIGN KEY ("uploadedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "episode_likes" ADD CONSTRAINT "episode_likes_episodeId_fkey" FOREIGN KEY ("episodeId") REFERENCES "episodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "episode_likes" ADD CONSTRAINT "episode_likes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "episode_likes" ADD CONSTRAINT "episode_likes_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "user_sessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "episode_likes" ADD CONSTRAINT "episode_likes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "episode_likes" ADD CONSTRAINT "episode_likes_episodeId_fkey" FOREIGN KEY ("episodeId") REFERENCES "episodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "videos" ADD CONSTRAINT "videos_episodeId_fkey" FOREIGN KEY ("episodeId") REFERENCES "episodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -775,23 +742,23 @@ ALTER TABLE "videos" ADD CONSTRAINT "videos_uploadedBy_fkey" FOREIGN KEY ("uploa
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "video_services" ADD CONSTRAINT "video_services_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_langPreference_fkey" FOREIGN KEY ("langPreference") REFERENCES "languages"("code") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_serviceDefaultId_fkey" FOREIGN KEY ("serviceDefaultId") REFERENCES "video_services"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_role_assignments" ADD CONSTRAINT "user_role_assignments_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "user_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "user_role_assignments" ADD CONSTRAINT "user_role_assignments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_role_assignments" ADD CONSTRAINT "user_role_assignments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "user_role_assignments" ADD CONSTRAINT "user_role_assignments_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "user_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_notifications" ADD CONSTRAINT "user_notifications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -800,35 +767,29 @@ ALTER TABLE "user_notifications" ADD CONSTRAINT "user_notifications_userId_fkey"
|
||||
ALTER TABLE "user_sessions" ADD CONSTRAINT "user_sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_logs" ADD CONSTRAINT "user_logs_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "user_sessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "user_logs" ADD CONSTRAINT "user_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_logs" ADD CONSTRAINT "user_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "user_logs" ADD CONSTRAINT "user_logs_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "user_sessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "collections" ADD CONSTRAINT "collections_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CollectionMedia" ADD CONSTRAINT "CollectionMedia_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "collections"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CollectionMedia" ADD CONSTRAINT "CollectionMedia_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "medias"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "watch_histories" ADD CONSTRAINT "watch_histories_id_fkey" FOREIGN KEY ("id") REFERENCES "episodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "watch_histories" ADD CONSTRAINT "watch_histories_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "user_sessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "watch_histories" ADD CONSTRAINT "watch_histories_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "movie_reviews" ADD CONSTRAINT "movie_reviews_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "watch_histories" ADD CONSTRAINT "watch_histories_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "user_sessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "movie_reviews" ADD CONSTRAINT "movie_reviews_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "medias"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "movie_reviews" ADD CONSTRAINT "movie_reviews_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comments" ADD CONSTRAINT "comments_episodeId_fkey" FOREIGN KEY ("episodeId") REFERENCES "episodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
@ -845,10 +806,10 @@ ALTER TABLE "comment_likes" ADD CONSTRAINT "comment_likes_commentId_fkey" FOREIG
|
||||
ALTER TABLE "comment_likes" ADD CONSTRAINT "comment_likes_userLiked_fkey" FOREIGN KEY ("userLiked") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment_reports" ADD CONSTRAINT "comment_reports_approvedBy_fkey" FOREIGN KEY ("approvedBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "comment_reports" ADD CONSTRAINT "comment_reports_userReporter_fkey" FOREIGN KEY ("userReporter") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment_reports" ADD CONSTRAINT "comment_reports_userReporter_fkey" FOREIGN KEY ("userReporter") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "comment_reports" ADD CONSTRAINT "comment_reports_approvedBy_fkey" FOREIGN KEY ("approvedBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "languages" ADD CONSTRAINT "languages_craetedBy_fkey" FOREIGN KEY ("craetedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -859,12 +820,6 @@ ALTER TABLE "email_system_accounts" ADD CONSTRAINT "email_system_accounts_create
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "email_system_histories" ADD CONSTRAINT "email_system_histories_userRelated_fkey" FOREIGN KEY ("userRelated") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "hero_banner" ADD CONSTRAINT "hero_banner_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "hero_banner" ADD CONSTRAINT "hero_banner_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "medias"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "system_notifications" ADD CONSTRAINT "system_notifications_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
@ -895,6 +850,12 @@ ALTER TABLE "_MediaCharacters" ADD CONSTRAINT "_MediaCharacters_A_fkey" FOREIGN
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_MediaCharacters" ADD CONSTRAINT "_MediaCharacters_B_fkey" FOREIGN KEY ("B") REFERENCES "medias"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_MediaCollections" ADD CONSTRAINT "_MediaCollections_A_fkey" FOREIGN KEY ("A") REFERENCES "collections"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_MediaCollections" ADD CONSTRAINT "_MediaCollections_B_fkey" FOREIGN KEY ("B") REFERENCES "medias"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_UserSelectedSharingCollention" ADD CONSTRAINT "_UserSelectedSharingCollention_A_fkey" FOREIGN KEY ("A") REFERENCES "collections"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `code` on the `videos` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[serviceId,videoCode]` on the table `videos` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `videoCode` to the `videos` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "videos_serviceId_code_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "videos" RENAME COLUMN "code" TO "videoCode";
|
||||
|
||||
-- CreateIndex
|
||||
DROP INDEX IF EXISTS "videos_serviceId_code_key";
|
||||
CREATE UNIQUE INDEX "videos_serviceId_videoCode_key" ON "videos"("serviceId", "videoCode");
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "videos" ADD COLUMN "thumbnailCode" TEXT;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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");
|
||||
@ -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");
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "hero_banner" ADD COLUMN "tags" TEXT[];
|
||||
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[key]` on the table `system_preferences` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "system_preferences_key_key" ON "system_preferences"("key");
|
||||
@ -1,14 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The values [Winter,Spring,Summer,Fall] on the enum `Season` will be removed. If these variants are still used in the database, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterEnum
|
||||
BEGIN;
|
||||
CREATE TYPE "Season_new" AS ENUM ('winter', 'spring', 'summer', 'fall');
|
||||
ALTER TABLE "medias" ALTER COLUMN "season" TYPE "Season_new" USING ("season"::text::"Season_new");
|
||||
ALTER TYPE "Season" RENAME TO "Season_old";
|
||||
ALTER TYPE "Season_new" RENAME TO "Season";
|
||||
DROP TYPE "public"."Season_old";
|
||||
COMMIT;
|
||||
1477
prisma/schema.prisma
1477
prisma/schema.prisma
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
import { Context } from "elysia";
|
||||
import { UserHeaderInformation } from "./types";
|
||||
|
||||
export interface ClientInfoHeader {
|
||||
@ -9,14 +10,25 @@ export interface ClientInfoHeader {
|
||||
ip: string;
|
||||
}
|
||||
|
||||
export const getUserHeaderInformation = (clientInfo: string): UserHeaderInformation => {
|
||||
const clientInfoHeader = (JSON.parse(clientInfo) as ClientInfoHeader) ?? ("unknown" as string);
|
||||
export const getUserHeaderInformation = (
|
||||
ctx: Context,
|
||||
): UserHeaderInformation => {
|
||||
const clientInfoHeader =
|
||||
(JSON.parse(
|
||||
ctx.request.headers.get("x-client-info") as string,
|
||||
) as ClientInfoHeader) ?? ("unknown" as string);
|
||||
|
||||
const userHeaderInformation = {
|
||||
ip: clientInfoHeader.ip ?? "unknown",
|
||||
deviceType: clientInfoHeader.deviceType ?? "unknown",
|
||||
deviceOS: (clientInfoHeader.os ?? "unknown") + " " + (clientInfoHeader.osVersion ?? "unknown"),
|
||||
browser: (clientInfoHeader.browser ?? "unknown") + " " + (clientInfoHeader.browserVersion ?? "unknown"),
|
||||
deviceOS:
|
||||
(clientInfoHeader.os ?? "unknown") +
|
||||
" " +
|
||||
(clientInfoHeader.osVersion ?? "unknown"),
|
||||
browser:
|
||||
(clientInfoHeader.browser ?? "unknown") +
|
||||
" " +
|
||||
(clientInfoHeader.browserVersion ?? "unknown"),
|
||||
};
|
||||
|
||||
return userHeaderInformation;
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { Context } from "elysia";
|
||||
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { githubCallbackService } from "../services/http/githubCallback.service";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
|
||||
import { githubCallbackSchema } from "../schemas/githubCallback.schema";
|
||||
|
||||
export const githubCallbackController = async (ctx: {
|
||||
set: Context["set"];
|
||||
query: Static<typeof githubCallbackSchema.query>;
|
||||
headers: Static<typeof githubCallbackSchema.headers>;
|
||||
}) => {
|
||||
export const githubCallbackController = async (
|
||||
ctx: Context & { query: { code: string; callbackURI: string } }
|
||||
) => {
|
||||
try {
|
||||
const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]);
|
||||
const userHeaderInfo = getUserHeaderInformation(ctx);
|
||||
|
||||
const authToken = await githubCallbackService(ctx.query, userHeaderInfo);
|
||||
return returnWriteResponse(ctx.set, 200, "Authenticated successfully!", {
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { Context } from "elysia";
|
||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { githubRequestService } from "../services/http/githubRequest.service";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { githubRequestSchema } from "../schemas/githubRequest.schema";
|
||||
|
||||
export const githubRequestController = async (ctx: {
|
||||
set: Context["set"];
|
||||
query: Static<typeof githubRequestSchema.query>;
|
||||
}) => {
|
||||
export const githubRequestController = async (
|
||||
ctx: Context & { query: { callback?: string } },
|
||||
) => {
|
||||
try {
|
||||
const loginUrl = await githubRequestService(ctx.query.callback);
|
||||
return returnReadResponse(ctx.set, 200, "GitHub login URL created successfully.", {
|
||||
endpointUrl: loginUrl,
|
||||
});
|
||||
return returnReadResponse(
|
||||
ctx.set,
|
||||
200,
|
||||
"Login URL generated successfully",
|
||||
{
|
||||
endpointUrl: loginUrl,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return mainErrorHandler(ctx.set, error);
|
||||
}
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { Context } from "elysia";
|
||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { googleCallbackService } from "../services/http/googleCallback.service";
|
||||
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
|
||||
import { googleCallbackSchema } from "../schemas/googleCallback.schema";
|
||||
|
||||
export const googleCallbackController = async (ctx: {
|
||||
set: Context["set"];
|
||||
query: Static<typeof googleCallbackSchema.query>;
|
||||
headers: Static<typeof googleCallbackSchema.headers>;
|
||||
}) => {
|
||||
export const googleCallbackController = async (
|
||||
ctx: Context & { query: { code: string; state: string; callbackURI: string } }
|
||||
) => {
|
||||
try {
|
||||
const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]);
|
||||
const userHeaderInfo = getUserHeaderInformation(ctx);
|
||||
|
||||
const authToken = await googleCallbackService(ctx.query, userHeaderInfo);
|
||||
return returnReadResponse(ctx.set, 200, "Authentication successful!", {
|
||||
return returnReadResponse(ctx.set, 200, "Authenticated successfully!", {
|
||||
authToken,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { Context } from "elysia";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { googleRequestService } from "../services/http/googleRequest.service";
|
||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { googleRequestSchema } from "../schemas/googleRequest.schema";
|
||||
|
||||
export const googleRequestController = async (ctx: {
|
||||
set: Context["set"];
|
||||
query: Static<typeof googleRequestSchema.query>;
|
||||
}) => {
|
||||
export const googleRequestController = async (
|
||||
ctx: Context & { query: { callback?: string } }
|
||||
) => {
|
||||
try {
|
||||
const loginUrl = await googleRequestService(ctx.query.callback);
|
||||
return returnReadResponse(ctx.set, 200, "Google login URL created successfully.", {
|
||||
return returnReadResponse(ctx.set, 200, "Google login url created!", {
|
||||
endpointUrl: loginUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@ -9,19 +9,13 @@ import { tokenValidationController } from "./controllers/tokenValidation.control
|
||||
import { logoutController } from "./controllers/logout.controller";
|
||||
import { tokenValidationSchema } from "./schemas/tokenValidation.schema";
|
||||
import { getOauthProvidersSchema } from "./schemas/getOauthProviders.schema";
|
||||
import { getCallbackProviderUrlSchema } from "./schemas/getCallbackProviderUrl.schema";
|
||||
import { googleRequestSchema } from "./schemas/googleRequest.schema";
|
||||
import { googleCallbackSchema } from "./schemas/googleCallback.schema";
|
||||
import { githubRequestSchema } from "./schemas/githubRequest.schema";
|
||||
import { githubCallbackSchema } from "./schemas/githubCallback.schema";
|
||||
import { logoutSchema } from "./schemas/logout.schema";
|
||||
|
||||
export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] })
|
||||
.post("/token/validate", tokenValidationController, tokenValidationSchema)
|
||||
.get("/providers", getOauthProvidersController, getOauthProvidersSchema)
|
||||
.get("/providers/:name/callback", getCallbackProviderUrlController, getCallbackProviderUrlSchema)
|
||||
.get("/google", googleRequestController, googleRequestSchema)
|
||||
.get("/google/callback", googleCallbackController, googleCallbackSchema)
|
||||
.get("/github", githubRequestController, githubRequestSchema)
|
||||
.get("/github/callback", githubCallbackController, githubCallbackSchema)
|
||||
.post("/logout", logoutController, logoutSchema);
|
||||
.get("/providers/:name/callback", getCallbackProviderUrlController)
|
||||
.get("/github", githubRequestController)
|
||||
.get("/github/callback", githubCallbackController)
|
||||
.get("/google", googleRequestController)
|
||||
.get("/google/callback", googleCallbackController)
|
||||
.post("/logout", logoutController);
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const getCallbackProviderUrlSchema = {
|
||||
detail: {
|
||||
summary: "Get the callback URL of oauth provider",
|
||||
description:
|
||||
"After users have successfully completed the authentication process on the OAuth provider page, they will be redirected to the callback page on the frontend. This endpoint aims to obtain the actual endpoint for each OAuth response handler.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "The callback URL on the provider has been found.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
status: {
|
||||
type: "number",
|
||||
default: 200,
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
default: "The callback URL on the provider has been found.",
|
||||
},
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
callback_url: {
|
||||
type: "string",
|
||||
description: "The callback URL on the provider.",
|
||||
example: "auth/google/callback",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,3 +1,4 @@
|
||||
import { success } from "zod";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const getOauthProvidersSchema = {
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const githubCallbackSchema = {
|
||||
headers: t.Object({
|
||||
"x-client-info": t.String({
|
||||
examples: [
|
||||
'{"os":"Windows","osVersion":"10","browser":"Chrome","browserVersion":"89.0.4389.82","deviceType":"Desktop","ip":"192.168.1.1"}',
|
||||
],
|
||||
}),
|
||||
}),
|
||||
query: t.Object({
|
||||
code: t.String({ examples: ["4/0AY0e-xxxxxxxxx"] }),
|
||||
callbackURI: t.String({ examples: ["https://example.com/auth/github/callback"] }),
|
||||
}),
|
||||
detail: {
|
||||
summary: "GitHub OAuth callback endpoint",
|
||||
description:
|
||||
"Handles the callback from GitHub OAuth and processes the authentication response. This endpoint also processes the account provisioning if the user is logging in for the first time.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Authentication successful",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
example: true,
|
||||
},
|
||||
status: {
|
||||
type: "number",
|
||||
example: 200,
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
example: "Authentication successful",
|
||||
},
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
authToken: {
|
||||
type: "string",
|
||||
description: "JWT token for authenticated user",
|
||||
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,54 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const githubRequestSchema = {
|
||||
query: t.Object({
|
||||
callback: t.Optional(
|
||||
t.String({
|
||||
description: "The callback URL to redirect after GitHub authentication. It should be URL-encoded if provided.",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Initiate GitHub OAuth flow",
|
||||
description:
|
||||
"This endpoint initiates the GitHub OAuth flow by redirecting the user to GitHub's authentication page.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "GitHub login URL created successfully.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
status: {
|
||||
type: "number",
|
||||
default: 200,
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
default: "GitHub login URL created successfully.",
|
||||
},
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
endpointUrl: {
|
||||
type: "string",
|
||||
description: "The URL to redirect the user for GitHub authentication.",
|
||||
example:
|
||||
"https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=user:email",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,58 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const googleCallbackSchema = {
|
||||
headers: t.Object({
|
||||
"x-client-info": t.String({
|
||||
examples: [
|
||||
'{"os":"Windows","osVersion":"10","browser":"Chrome","browserVersion":"89.0.4389.82","deviceType":"Desktop","ip":"192.168.1.1"}',
|
||||
],
|
||||
}),
|
||||
}),
|
||||
query: t.Object({
|
||||
code: t.String({ examples: ["4/0AY0e-xxxxxxxxx"] }),
|
||||
state: t.String({ examples: ["random_state_string"] }),
|
||||
callbackURI: t.String({ examples: ["https://example.com/auth/google/callback"] }),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Google OAuth callback endpoint",
|
||||
description:
|
||||
"Handles the callback from Google OAuth and processes the authentication response. This endpoint also processes the account provisioning if the user is logging in for the first time.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Authentication successful",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
example: true,
|
||||
},
|
||||
status: {
|
||||
type: "number",
|
||||
example: 200,
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
example: "Authentication successful",
|
||||
},
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
authToken: {
|
||||
type: "string",
|
||||
description: "JWT token for authenticated user",
|
||||
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,54 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const googleRequestSchema = {
|
||||
query: t.Object({
|
||||
callback: t.Optional(
|
||||
t.String({
|
||||
description: "The callback URL to redirect after Google authentication. It should be URL-encoded if provided.",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Initiate Google OAuth flow",
|
||||
description:
|
||||
"This endpoint initiates the Google OAuth flow by redirecting the user to Google's authentication page.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Google login URL created successfully.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
},
|
||||
status: {
|
||||
type: "number",
|
||||
default: 200,
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
default: "Google login URL created successfully.",
|
||||
},
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
endpointUrl: {
|
||||
type: "string",
|
||||
description: "The URL to redirect the user for Google authentication.",
|
||||
example:
|
||||
"https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=email%20profile",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,97 +0,0 @@
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const logoutSchema = {
|
||||
detail: {
|
||||
summary: "Logout endpoint",
|
||||
description: "Logs out the authenticated user by invalidating their session or token.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Logout successful",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
example: true,
|
||||
},
|
||||
status: {
|
||||
type: "number",
|
||||
example: 200,
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
example: "Logout successful",
|
||||
},
|
||||
data: {
|
||||
type: "object",
|
||||
description: "Details about the logout operation. This only returned in development environment.",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
example: "123e4567-e89b-12d3-a456-426614174000",
|
||||
},
|
||||
isAuthenticated: {
|
||||
type: "boolean",
|
||||
example: false,
|
||||
},
|
||||
validUntil: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
example: "2024-12-31T23:59:59Z",
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
example: "user_12345",
|
||||
},
|
||||
deletedAt: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
example: "2024-01-02T12:00:00Z",
|
||||
},
|
||||
createdAt: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
example: "2024-01-01T12:00:00Z",
|
||||
},
|
||||
updatedAt: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
example: "2024-01-02T12:00:00Z",
|
||||
},
|
||||
deviceType: {
|
||||
type: "string",
|
||||
example: "Desktop",
|
||||
},
|
||||
deviceOs: {
|
||||
type: "string",
|
||||
example: "Windows 10",
|
||||
},
|
||||
deviceIp: {
|
||||
type: "string",
|
||||
example: "192.168.1.1",
|
||||
},
|
||||
browser: {
|
||||
type: "string",
|
||||
example: "Chrome 89.0.4389.82",
|
||||
},
|
||||
isOnline: {
|
||||
type: "boolean",
|
||||
example: false,
|
||||
},
|
||||
lastOnline: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
example: "2024-01-02T12:00:00Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,22 +0,0 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { addItemToCollectionBySytemSchema } from "../schemas/addItemToCollectionBySytem.schema";
|
||||
import { addItemToCollectionBySystemService } from "../services/addItemToCollectionBySystem.service";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
|
||||
export const addItemToCollectionBySytemController = async (ctx: {
|
||||
set: Context["set"];
|
||||
headers: Static<typeof addItemToCollectionBySytemSchema.headers>;
|
||||
body: Static<typeof addItemToCollectionBySytemSchema.body>;
|
||||
}) => {
|
||||
try {
|
||||
const savedItem = await addItemToCollectionBySystemService({
|
||||
cookie: ctx.headers.cookie,
|
||||
collectionName: ctx.body.name,
|
||||
mediaId: ctx.body.itemId,
|
||||
});
|
||||
return returnWriteResponse(ctx.set, 200, "Item added to collection successfully", savedItem);
|
||||
} catch (error) {
|
||||
return mainErrorHandler(ctx.set, error);
|
||||
}
|
||||
};
|
||||
@ -1,22 +0,0 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { removeItemFromCollectionBySystemService } from "../services/removeItemFromCollectionBySystem.service";
|
||||
import { removeItemFromCollectionBySytemSchema } from "../schemas/removeItemFromCollectionBySytem.schema";
|
||||
|
||||
export const removeItemFromCollectionBySytemController = async (ctx: {
|
||||
set: Context["set"];
|
||||
headers: Static<typeof removeItemFromCollectionBySytemSchema.headers>;
|
||||
body: Static<typeof removeItemFromCollectionBySytemSchema.body>;
|
||||
}) => {
|
||||
try {
|
||||
const removedItem = await removeItemFromCollectionBySystemService({
|
||||
cookie: ctx.headers.cookie,
|
||||
collectionName: ctx.body.name,
|
||||
mediaId: ctx.body.itemId,
|
||||
});
|
||||
return returnWriteResponse(ctx.set, 200, "Item removed from collection successfully", removedItem);
|
||||
} catch (error) {
|
||||
return mainErrorHandler(ctx.set, error);
|
||||
}
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
import Elysia from "elysia";
|
||||
import { addItemToCollectionBySytemController } from "./controllers/addItemToCollectionBySytem.controller";
|
||||
import { addItemToCollectionBySytemSchema } from "./schemas/addItemToCollectionBySytem.schema";
|
||||
import { removeItemFromCollectionBySytemController } from "./controllers/removeItemFromCollectionBySytem.controller";
|
||||
import { removeItemFromCollectionBySytemSchema } from "./schemas/removeItemFromCollectionBySytem.schema";
|
||||
|
||||
export const collectionModule = new Elysia({ prefix: "/collections", tags: ["Collections"] })
|
||||
.post("/sys", addItemToCollectionBySytemController, addItemToCollectionBySytemSchema)
|
||||
.delete("/sys", removeItemFromCollectionBySytemController, removeItemFromCollectionBySytemSchema);
|
||||
@ -1,31 +0,0 @@
|
||||
import slugify from "slugify";
|
||||
import { AppError } from "../../../helpers/error/instances/app";
|
||||
import { prisma } from "../../../utils/databases/prisma/connection";
|
||||
|
||||
export type DeleteUserCollectionBySystemPayload = {
|
||||
userId: string;
|
||||
collectionName: string;
|
||||
itemId: string;
|
||||
};
|
||||
|
||||
export const deleteItemInUserCollectionBySystemRepository = async (payload: DeleteUserCollectionBySystemPayload) => {
|
||||
try {
|
||||
return await prisma.collection.update({
|
||||
where: {
|
||||
slug_ownerId: {
|
||||
slug: slugify(payload.collectionName, { lower: true }),
|
||||
ownerId: payload.userId,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
media_saved: {
|
||||
deleteMany: {
|
||||
mediaId: payload.itemId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
throw new AppError(500, "Failed to remove item from collection", error);
|
||||
}
|
||||
};
|
||||
@ -1,60 +0,0 @@
|
||||
import slugify from "slugify";
|
||||
import { AppError } from "../../../helpers/error/instances/app";
|
||||
import { prisma } from "../../../utils/databases/prisma/connection";
|
||||
import { generateUUIDv7 } from "../../../helpers/databases/uuidv7";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export interface UpsertUserCollectionRepositoryPayload {
|
||||
userId: string;
|
||||
collectionName: string;
|
||||
mediaConnectId: string;
|
||||
}
|
||||
|
||||
export const upsertUserCollectionBySystemRepository = async (payload: UpsertUserCollectionRepositoryPayload) => {
|
||||
try {
|
||||
return await prisma.collection.upsert({
|
||||
where: {
|
||||
slug_ownerId: {
|
||||
slug: slugify(payload.collectionName, { lower: true }),
|
||||
ownerId: payload.userId,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
media_saved: {
|
||||
create: {
|
||||
id: generateUUIDv7(),
|
||||
media: {
|
||||
connect: {
|
||||
id: payload.mediaConnectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
id: generateUUIDv7(),
|
||||
name: payload.collectionName,
|
||||
slug: slugify(payload.collectionName, { lower: true }),
|
||||
owner: {
|
||||
connect: {
|
||||
id: payload.userId,
|
||||
},
|
||||
},
|
||||
media_saved: {
|
||||
create: {
|
||||
id: generateUUIDv7(),
|
||||
media: {
|
||||
connect: {
|
||||
id: payload.mediaConnectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002")
|
||||
throw new AppError(400, "Media item is already in the collection");
|
||||
throw new AppError(500, "Failed to upsert user collection");
|
||||
}
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const addItemToCollectionBySytemSchema = {
|
||||
headers: t.Object({
|
||||
cookie: t.String({ description: "Authentication token in cookie format, e.g., auth_token=your_jwt_token;" }),
|
||||
}),
|
||||
body: t.Object({
|
||||
name: t.String({ description: "Name of the collection to which the item will be added" }),
|
||||
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;
|
||||
@ -1,33 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const removeItemFromCollectionBySytemSchema = {
|
||||
headers: t.Object({
|
||||
cookie: t.String({ description: "Authentication token in cookie format, e.g., auth_token=your_jwt_token;" }),
|
||||
}),
|
||||
body: t.Object({
|
||||
name: t.String({ description: "Name of the collection to which the item will be added" }),
|
||||
itemId: t.String({ description: "ID of the item to be added to the collection", examples: ["12345"] }),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Remove an item from a collection",
|
||||
description: "Removes a specified item from a collection identified by its name.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "The item was successfully removed from the collection.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: { type: "boolean", example: true },
|
||||
status: { type: "number", example: 200 },
|
||||
message: { type: "string", example: "Item removed from collection successfully" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,24 +0,0 @@
|
||||
import { parse } from "cookie";
|
||||
import { tokenValidationService } from "../../auth/services/http/tokenValidation.service";
|
||||
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
|
||||
import { upsertUserCollectionBySystemRepository } from "../repositories/upsertUserCollectionBySystem.repository";
|
||||
|
||||
export type AddItemToCollectionPayload = {
|
||||
cookie: string;
|
||||
collectionName: string;
|
||||
mediaId: string;
|
||||
};
|
||||
|
||||
export const addItemToCollectionBySystemService = async (payload: AddItemToCollectionPayload) => {
|
||||
try {
|
||||
const { auth_token } = parse(payload.cookie);
|
||||
const userData = await tokenValidationService(auth_token as string);
|
||||
return await upsertUserCollectionBySystemRepository({
|
||||
userId: userData.user.id,
|
||||
collectionName: payload.collectionName,
|
||||
mediaConnectId: payload.mediaId,
|
||||
});
|
||||
} catch (error) {
|
||||
ErrorForwarder(error);
|
||||
}
|
||||
};
|
||||
@ -1,24 +0,0 @@
|
||||
import { parse } from "cookie";
|
||||
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
|
||||
import { tokenValidationService } from "../../auth/services/http/tokenValidation.service";
|
||||
import { deleteItemInUserCollectionBySystemRepository } from "../repositories/deleteItemInUserCollectionBySystem.repository";
|
||||
|
||||
export type RemoveItemFromCollectionPayload = {
|
||||
cookie: string;
|
||||
collectionName: string;
|
||||
mediaId: string;
|
||||
};
|
||||
|
||||
export const removeItemFromCollectionBySystemService = async (payload: RemoveItemFromCollectionPayload) => {
|
||||
try {
|
||||
const { auth_token } = parse(payload.cookie);
|
||||
const { user } = await tokenValidationService(auth_token as string);
|
||||
return await deleteItemInUserCollectionBySystemRepository({
|
||||
userId: user.id,
|
||||
collectionName: payload.collectionName,
|
||||
itemId: payload.mediaId,
|
||||
});
|
||||
} catch (error) {
|
||||
ErrorForwarder(error);
|
||||
}
|
||||
};
|
||||
@ -1,14 +1,17 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { selectMediaIdFromSlugRepository } from "../../../media/repositories/SELECT/selectMediaIdFromSlug.repository";
|
||||
import { getMediaIdFromSlugRepository } from "../../../media/repositories/GET/getMediaIdFromSlug.repository";
|
||||
import { GetEpisodeDetailsParams } from "../../controllers/getEpisodeDetails.controller";
|
||||
import { getEpisodeDetailsRepository } from "../../repositories/GET/getEpisodeDetails.repository";
|
||||
|
||||
export const getEpisodeDetailsService = async (params: GetEpisodeDetailsParams) => {
|
||||
export const getEpisodeDetailsService = async (
|
||||
params: GetEpisodeDetailsParams,
|
||||
) => {
|
||||
try {
|
||||
if (!params.mediaSlug || !params.episode) throw new AppError(400, "Media slug and episode are required.");
|
||||
if (!params.mediaSlug || !params.episode)
|
||||
throw new AppError(400, "Media slug and episode are required.");
|
||||
|
||||
const mediaId = await selectMediaIdFromSlugRepository(params.mediaSlug);
|
||||
const mediaId = await getMediaIdFromSlugRepository(params.mediaSlug);
|
||||
if (!mediaId?.id) throw new AppError(404, "Media not found.");
|
||||
|
||||
const result = await getEpisodeDetailsRepository({
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { clearHeroBannerService } from "../services/clearHeroBanner.service";
|
||||
|
||||
export const clearHeroBannerController = async (ctx: { set: Context["set"] }) => {
|
||||
const cacheCleared = await clearHeroBannerService();
|
||||
return returnWriteResponse(ctx.set, 200, "Hero banner cache flushed successfully", cacheCleared);
|
||||
};
|
||||
@ -1,4 +0,0 @@
|
||||
import Elysia from "elysia";
|
||||
import { clearHeroBannerController } from "./controllers/clearHeroBanner.controller";
|
||||
|
||||
export const flushCacheModule = new Elysia({ prefix: "/flush-cache" }).put("/hero-banner", clearHeroBannerController);
|
||||
@ -1,12 +0,0 @@
|
||||
import { redisKey } from "../../../config/redis/key";
|
||||
import { AppError } from "../../../helpers/error/instances/app";
|
||||
import { redis } from "../../../utils/databases/redis/connection";
|
||||
|
||||
export const clearHeroBannerService = async () => {
|
||||
try {
|
||||
const cache = await redis.del(redisKey.find((key) => key.name === "HERO_BANNER")?.key || "");
|
||||
return cache > 0; // Returns true if cache was cleared, false if it was not found
|
||||
} catch (error) {
|
||||
throw new AppError(500, "Failed to clear hero banner cache", error);
|
||||
}
|
||||
};
|
||||
@ -1,16 +1,17 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { Context } from "elysia";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { getActiveHeroBannerService } from "../services/getActiveHeroBanner.service";
|
||||
import { getActiveHeroBannerSchema } from "../schemas/getActiveHeroBanner.schema";
|
||||
|
||||
export const getActiveHeroBannerController = async (ctx: {
|
||||
set: Context["set"];
|
||||
header: Static<typeof getActiveHeroBannerSchema.headers>;
|
||||
}) => {
|
||||
export const getActiveHeroBannerController = async (ctx: Context) => {
|
||||
try {
|
||||
const response = await getActiveHeroBannerService({ cookie: ctx.header?.cookie });
|
||||
return returnReadResponse(ctx.set, 200, "Active hero banners fetched successfully", response);
|
||||
const response = await getActiveHeroBannerService();
|
||||
return returnReadResponse(
|
||||
ctx.set,
|
||||
200,
|
||||
"Active hero banners fetched successfully",
|
||||
response,
|
||||
);
|
||||
} catch (error) {
|
||||
return mainErrorHandler(ctx.set, error);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { prisma } from "../../../../utils/databases/prisma/connection";
|
||||
|
||||
export const findAllActiveHeroBannerRepository = async (userId?: string) => {
|
||||
export const findAllActiveHeroBannerRepository = async () => {
|
||||
try {
|
||||
return await prisma.heroBanner.findMany({
|
||||
where: {
|
||||
@ -20,36 +20,6 @@ export const findAllActiveHeroBannerRepository = async (userId?: string) => {
|
||||
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,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
inCollections: {
|
||||
where: {
|
||||
collection: {
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
throw new AppError(500, "Failed to fetch active hero banners", error);
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const getActiveHeroBannerSchema = {
|
||||
headers: t.Object({
|
||||
cookie: t.Optional(t.String()),
|
||||
}),
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,44 +1,33 @@
|
||||
import { redisKey } from "../../../config/redis/key";
|
||||
import { AppError } from "../../../helpers/error/instances/app";
|
||||
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
|
||||
import { tokenValidationService } from "../../auth/services/http/tokenValidation.service";
|
||||
import { redis } from "../../../utils/databases/redis/connection";
|
||||
import { findSystemPreferenceService } from "../../systemPreference/services/internal/findSystemPreference.service";
|
||||
import { findAllActiveHeroBannerRepository } from "../repositories/GET/findAllActiveHeroBanner.repository";
|
||||
|
||||
export const getActiveHeroBannerService = async ({ cookie }: { cookie?: string }) => {
|
||||
export const getActiveHeroBannerService = async () => {
|
||||
try {
|
||||
const userId = cookie ? (await tokenValidationService(cookie)).user.id : undefined;
|
||||
|
||||
// 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");
|
||||
|
||||
// Don’t implement caching just yet; implement collection caching first, then implement banner caching.
|
||||
// Please note that currently, a database query is still required to check the collections.
|
||||
// // Try to get active banners from Redis cache
|
||||
// const cachedBanners = await redis.get(`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`);
|
||||
// if (cachedBanners) return JSON.parse(cachedBanners);
|
||||
// Try to get active banners from Redis cache
|
||||
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(userId);
|
||||
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,
|
||||
})),
|
||||
isInCollection: banner.media._count.inCollections > 0,
|
||||
}));
|
||||
|
||||
// await redis.set(
|
||||
// `${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`,
|
||||
// JSON.stringify(constructedBanners),
|
||||
// );
|
||||
|
||||
return constructedBanners;
|
||||
const activeBanners = await findAllActiveHeroBannerRepository();
|
||||
await redis.set(
|
||||
`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`,
|
||||
JSON.stringify(activeBanners),
|
||||
);
|
||||
return activeBanners;
|
||||
} catch (error) {
|
||||
ErrorForwarder(error);
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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";
|
||||
import { Static } from "elysia";
|
||||
import { createHeroBannerSchema } from "../schemas/createHeroBanner.schema";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export const insertHeroBannerRepository = async (payload: Static<typeof createHeroBannerSchema.body>) => {
|
||||
export const insertHeroBannerRepository = async (
|
||||
payload: Omit<Prisma.HeroBannerCreateInput, "id" | "createdBy">,
|
||||
) => {
|
||||
try {
|
||||
return await prisma.heroBanner.create({
|
||||
data: {
|
||||
@ -16,9 +16,6 @@ export const insertHeroBannerRepository = async (payload: Static<typeof createHe
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
||||
throw new AppError(400, "A hero banner with the order priority already exists", error);
|
||||
}
|
||||
throw new AppError(500, "Failed to insert hero banner", error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,18 +3,45 @@ import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const createHeroBannerSchema = {
|
||||
body: t.Object({
|
||||
orderPriority: t.Optional(
|
||||
t.Number({ description: "The priority order of the hero banner. Lower numbers indicate higher priority." }),
|
||||
),
|
||||
mediaId: t.String({ description: "The ID of the media associated with the hero banner" }),
|
||||
imageUrl: t.Optional(
|
||||
t.String({
|
||||
description:
|
||||
"The URL of the image used in the hero banner. If not provided, a thumbnail image of the media will be used.",
|
||||
isClickable: t.Optional(
|
||||
t.Boolean({
|
||||
description: "Indicates whether the hero banner is clickable",
|
||||
}),
|
||||
),
|
||||
startDate: t.Date({ description: "The start date for the hero banner in ISO 8601 format" }),
|
||||
endDate: t.Date({ description: "The end date for the hero banner in ISO 8601 format" }),
|
||||
title: t.Optional(
|
||||
t.String({
|
||||
description: "The title of the hero banner",
|
||||
}),
|
||||
),
|
||||
tags: t.Array(t.String(), {
|
||||
description: "An array of tags associated with the hero banner",
|
||||
}),
|
||||
description: t.Optional(
|
||||
t.String({
|
||||
description: "A brief description of the hero banner",
|
||||
}),
|
||||
),
|
||||
buttonContent: t.Optional(
|
||||
t.String({
|
||||
description: "The text content of the button on the hero banner",
|
||||
}),
|
||||
),
|
||||
buttonLink: t.Optional(
|
||||
t.String({
|
||||
description: "The URL that the button on the hero banner links to",
|
||||
}),
|
||||
),
|
||||
imageUrl: t.Optional(
|
||||
t.String({
|
||||
description: "The URL of the image used in the hero banner",
|
||||
}),
|
||||
),
|
||||
startDate: t.String({
|
||||
description: "The start date for the hero banner in ISO 8601 format",
|
||||
}),
|
||||
endDate: t.String({
|
||||
description: "The end date for the hero banner in ISO 8601 format",
|
||||
}),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Create a new hero banner",
|
||||
@ -37,16 +64,17 @@ export const createHeroBannerSchema = {
|
||||
"The created hero banner object. This field is returned only if the environment is running in development mode.",
|
||||
properties: {
|
||||
id: { type: "string", description: "The ID of the created hero banner" },
|
||||
orderPriority: {
|
||||
type: "number",
|
||||
description: "The priority order of the hero banner. Lower numbers indicate higher priority.",
|
||||
},
|
||||
mediaId: { type: "string", description: "The ID of the media associated with the hero banner" },
|
||||
imageUrl: {
|
||||
type: "string",
|
||||
description:
|
||||
"The URL of the image used in the hero banner. If not provided, a thumbnail image of the media will be used.",
|
||||
isClickable: { type: "boolean", description: "Indicates whether the hero banner is clickable" },
|
||||
title: { type: "string", description: "The title of the hero banner" },
|
||||
tags: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "An array of tags associated with the hero banner",
|
||||
},
|
||||
description: { type: "string", description: "A brief description of the hero banner" },
|
||||
buttonContent: { type: "string", description: "The text content of the button on the hero banner" },
|
||||
buttonLink: { type: "string", description: "The URL that the button on the hero banner links to" },
|
||||
imageUrl: { type: "string", description: "The URL of the image used in the hero banner" },
|
||||
startDate: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
|
||||
@ -1,20 +1,27 @@
|
||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { MediaEpisodeInfoResponse } from "../../types/mediaEpisodeInfo.type";
|
||||
import { getMediaByMalIdRepository } from "../../../media/repositories/GET/getMediaByMalId.repository";
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { SystemAccountId } from "../../../../config/account/system";
|
||||
import { bulkInsertEpisodesRepository } from "../../repositories/bulkInsertEpisodes.repository";
|
||||
import { getEpisodeReferenceAPI } from "../../../../config/apis/jikan/episode.reference";
|
||||
import { selectMediaByMalIdRepository } from "../../../media/repositories/SELECT/selectMediaByMalId.repository";
|
||||
|
||||
export const bulkInsertEpisodeService = async (mal_id: number, page: number = 1) => {
|
||||
export const bulkInsertEpisodeService = async (
|
||||
mal_id: number,
|
||||
page: number = 1,
|
||||
) => {
|
||||
try {
|
||||
const episodeAPI = getEpisodeReferenceAPI(mal_id);
|
||||
const episodeData: MediaEpisodeInfoResponse = await fetch(
|
||||
`${episodeAPI.baseURL}${episodeAPI.getEpisodeList}?page=${page}`,
|
||||
).then((res) => res.json());
|
||||
|
||||
const mediaData = await selectMediaByMalIdRepository(mal_id);
|
||||
if (!mediaData) throw new AppError(404, `Media with Mal ID ${mal_id} not found in database`);
|
||||
const mediaData = await getMediaByMalIdRepository(mal_id);
|
||||
if (!mediaData)
|
||||
throw new AppError(
|
||||
404,
|
||||
`Media with Mal ID ${mal_id} not found in database`,
|
||||
);
|
||||
|
||||
const insertedEpisodeData = [];
|
||||
episodeData.data.forEach(async (episode) => {
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { Static } from "elysia";
|
||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { CreateHeroBannerRequestBody } from "../../controllers/createHeroBanner.controller";
|
||||
import { insertHeroBannerRepository } from "../../repositories/insertHeroBanner.repository";
|
||||
import { createHeroBannerSchema } from "../../schemas/createHeroBanner.schema";
|
||||
|
||||
export const createHeroBannerService = async (payload: Static<typeof createHeroBannerSchema.body>) => {
|
||||
export const createHeroBannerService = async (
|
||||
payload: CreateHeroBannerRequestBody,
|
||||
) => {
|
||||
try {
|
||||
return await insertHeroBannerRepository(payload);
|
||||
} catch (error) {
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { Context } from "elysia";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { getAllMediaService } from "../services/http/getAllMedia.service";
|
||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||
import { getAllMediaSchema } from "../schemas/getAllMedia.schema";
|
||||
|
||||
export const getAllMediaController = async (ctx: {
|
||||
set: Context["set"];
|
||||
query: Static<typeof getAllMediaSchema.query>;
|
||||
}) => {
|
||||
export const getAllMediaController = async (
|
||||
ctx: Context & { query: { page: string } },
|
||||
) => {
|
||||
try {
|
||||
const mediaData = await getAllMediaService(ctx.query.page);
|
||||
return returnReadResponse(ctx.set, 200, "Media fetched successfully", mediaData);
|
||||
return returnReadResponse(
|
||||
ctx.set,
|
||||
200,
|
||||
"Media fetched successfully",
|
||||
mediaData,
|
||||
);
|
||||
} catch (error) {
|
||||
return mainErrorHandler(ctx.set, error);
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { Context, Static } from "elysia";
|
||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||
import { getMediaBySlugSchema } from "../schemas/getMediaBySlug.schema";
|
||||
import { getMediaBySlugService } from "../services/http/getMediaBySlug.service";
|
||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||
|
||||
export const getMediaBySlugController = async (ctx: {
|
||||
set: Context["set"];
|
||||
params: Static<typeof getMediaBySlugSchema.params>;
|
||||
}) => {
|
||||
try {
|
||||
const mediaData = await getMediaBySlugService(ctx.params.slug);
|
||||
return returnReadResponse(ctx.set, 200, "Media fetched successfully", mediaData);
|
||||
} catch (error) {
|
||||
return mainErrorHandler(ctx.set, error);
|
||||
}
|
||||
};
|
||||
@ -1,9 +1,7 @@
|
||||
import Elysia from "elysia";
|
||||
import { getAllMediaController } from "./controllers/getAllMedia.controller";
|
||||
import { getMediaBySlugController } from "./controllers/getMediaBySlug.controller";
|
||||
import { getMediaBySlugSchema } from "./schemas/getMediaBySlug.schema";
|
||||
import { getAllMediaSchema } from "./schemas/getAllMedia.schema";
|
||||
|
||||
export const mediaModule = new Elysia({ prefix: "/media", tags: ["Media"] })
|
||||
.get("/", getAllMediaController, getAllMediaSchema)
|
||||
.get("/:slug", getMediaBySlugController, getMediaBySlugSchema);
|
||||
export const mediaModule = new Elysia({ prefix: "/media" }).get(
|
||||
"/",
|
||||
getAllMediaController,
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { mediaModel } from "../../model";
|
||||
|
||||
export const selectAllMediaRepository = async (page: number) => {
|
||||
export const getAllMediaRepository = async (page: number) => {
|
||||
try {
|
||||
const limit = 10;
|
||||
return await mediaModel.findMany({
|
||||
@ -1,7 +1,7 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { mediaModel } from "../../model";
|
||||
|
||||
export const selectMediaByMalIdRepository = async (mal_id: number) => {
|
||||
export const getMediaByMalIdRepository = async (mal_id: number) => {
|
||||
try {
|
||||
return await mediaModel.findUnique({
|
||||
where: { malId: mal_id },
|
||||
@ -1,7 +1,7 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { mediaModel } from "../../model";
|
||||
|
||||
export const selectMediaIdFromSlugRepository = async (slug: string) => {
|
||||
export const getMediaIdFromSlugRepository = async (slug: string) => {
|
||||
try {
|
||||
return await mediaModel.findUnique({
|
||||
where: { slug },
|
||||
@ -1,12 +0,0 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { prisma } from "../../../../utils/databases/prisma/connection";
|
||||
|
||||
export const selectMediaBySlugRepository = async (slug: string) => {
|
||||
try {
|
||||
return await prisma.media.findUnique({
|
||||
where: { slug },
|
||||
});
|
||||
} catch (error) {
|
||||
throw new AppError(500, "Failed to fetch media by slug", error);
|
||||
}
|
||||
};
|
||||
@ -1,58 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const getAllMediaSchema = {
|
||||
query: t.Object({
|
||||
page: t.String({ description: "The page number for pagination", default: "1" }),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Fetch all media items with pagination",
|
||||
description:
|
||||
"Fetch a paginated list of all media items. The 'page' query parameter can be used to specify the page number for pagination.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Media items fetched successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: { type: "boolean", example: true },
|
||||
status: { type: "number", example: 200 },
|
||||
message: { type: "string", example: "Media fetched successfully" },
|
||||
data: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { type: "string", example: "Finished Airing" },
|
||||
id: { type: "string", example: "12345" },
|
||||
title: { type: "string", example: "Example Media Title" },
|
||||
slug: { type: "string", example: "example-media-title" },
|
||||
malId: { type: "number", example: 67890 },
|
||||
pictureMedium: { type: "string", example: "https://example.com/medium.jpg" },
|
||||
pictureLarge: { type: "string", example: "https://example.com/large.jpg" },
|
||||
country: { type: "string", example: "JP" },
|
||||
score: { type: "number", example: 8.5 },
|
||||
startAiring: { type: "string", format: "date-time", example: "2023-01-01T00:00:00Z" },
|
||||
endAiring: { type: "string", format: "date-time", example: "2023-12-31T23:59:59Z" },
|
||||
synopsis: { type: "string", example: "This is an example synopsis of the media item." },
|
||||
ageRating: { type: "string", example: "PG-13" },
|
||||
mediaType: { type: "string", example: "Anime" },
|
||||
source: { type: "string", example: "Manga" },
|
||||
onDraft: { type: "boolean", example: false },
|
||||
uploadedBy: { type: "string", example: "admin" },
|
||||
deletedAt: { type: "string", format: "date-time", nullable: true, example: null },
|
||||
createdAt: { type: "string", format: "date-time", example: "2023-01-01T00:00:00Z" },
|
||||
updatedAt: { type: "string", format: "date-time", example: "2023-01-02T00:00:00Z" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,29 +0,0 @@
|
||||
import { t } from "elysia";
|
||||
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||
|
||||
export const getMediaBySlugSchema = {
|
||||
params: t.Object({
|
||||
slug: t.String({ description: "The slug of the media to fetch" }),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Fetch a media item by its slug",
|
||||
description: "Fetch the specified media item using its slug. This endpoint returns the media details if found.",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Media item fetched successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: { type: "boolean", example: true },
|
||||
status: { type: "number", example: 200 },
|
||||
message: { type: "string", example: "Media fetched successfully" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies AppRouteSchema;
|
||||
@ -1,11 +1,14 @@
|
||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { selectAllMediaRepository } from "../../repositories/SELECT/selectAllMedia.repository";
|
||||
import { getAllMediaRepository } from "../../repositories/GET/getAllMedia.repository";
|
||||
|
||||
export const getAllMediaService = async (pagination: string) => {
|
||||
try {
|
||||
const page = /^\d+$/.test(pagination) && Number(pagination) > 0 ? Number(pagination) : 1;
|
||||
const page =
|
||||
/^\d+$/.test(pagination) && Number(pagination) > 0
|
||||
? Number(pagination)
|
||||
: 1;
|
||||
|
||||
return selectAllMediaRepository(page);
|
||||
return getAllMediaRepository(page);
|
||||
} catch (error) {
|
||||
ErrorForwarder(error);
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { selectMediaBySlugRepository } from "../../repositories/SELECT/selectMediaBySlug.repository";
|
||||
|
||||
export const getMediaBySlugService = async (slug: string) => {
|
||||
try {
|
||||
const mediaData = await selectMediaBySlugRepository(slug);
|
||||
if (!mediaData) throw new AppError(404, "Media not found with the provided slug.");
|
||||
|
||||
return mediaData;
|
||||
} catch (error) {
|
||||
ErrorForwarder(error);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user