Compare commits
14 Commits
d767a0434c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dade012888 | |||
| 4001aec6ef | |||
| dd70f9f9d4 | |||
| 26909154ab | |||
| 7f6b1373f4 | |||
| 794a130562 | |||
| 6599fa8f79 | |||
| 27b66e6d34 | |||
| 1c097aac69 | |||
| b5a0c2eda6 | |||
| 864a919680 | |||
| da74f5e3e1 | |||
| dc350d006b | |||
| 97dc26ed82 |
@ -28,6 +28,7 @@ Table medias {
|
|||||||
deletedAt DateTime
|
deletedAt DateTime
|
||||||
createdAt DateTime [default: `now()`, not null]
|
createdAt DateTime [default: `now()`, not null]
|
||||||
updatedAt DateTime [default: `now()`, not null]
|
updatedAt DateTime [default: `now()`, not null]
|
||||||
|
bannerPromotion hero_banner [not null]
|
||||||
logs media_logs [not null]
|
logs media_logs [not null]
|
||||||
episodes episodes [not null]
|
episodes episodes [not null]
|
||||||
collections collections [not null]
|
collections collections [not null]
|
||||||
@ -496,12 +497,8 @@ Table email_system_histories {
|
|||||||
Table hero_banner {
|
Table hero_banner {
|
||||||
id String [pk]
|
id String [pk]
|
||||||
orderPriority Int [unique]
|
orderPriority Int [unique]
|
||||||
isClickable Boolean [not null, default: false]
|
mediaId String [not null]
|
||||||
title String
|
media medias [not null]
|
||||||
tags String[] [not null]
|
|
||||||
description String
|
|
||||||
buttonContent String
|
|
||||||
buttonLink String
|
|
||||||
imageUrl String
|
imageUrl String
|
||||||
startDate DateTime [not null]
|
startDate DateTime [not null]
|
||||||
endDate DateTime [not null]
|
endDate DateTime [not null]
|
||||||
@ -780,6 +777,8 @@ Ref: email_system_accounts.createdBy > users.id
|
|||||||
|
|
||||||
Ref: email_system_histories.userRelated > users.id
|
Ref: email_system_histories.userRelated > users.id
|
||||||
|
|
||||||
|
Ref: hero_banner.mediaId > medias.id
|
||||||
|
|
||||||
Ref: hero_banner.creatorId > users.id
|
Ref: hero_banner.creatorId > users.id
|
||||||
|
|
||||||
Ref: system_notifications.createdBy > users.id
|
Ref: system_notifications.createdBy > users.id
|
||||||
|
|||||||
@ -206,7 +206,8 @@ CREATE TABLE "videos" (
|
|||||||
"id" UUID NOT NULL,
|
"id" UUID NOT NULL,
|
||||||
"episodeId" UUID NOT NULL,
|
"episodeId" UUID NOT NULL,
|
||||||
"serviceId" UUID NOT NULL,
|
"serviceId" UUID NOT NULL,
|
||||||
"code" VARCHAR(255) NOT NULL,
|
"videoCode" VARCHAR(255) NOT NULL,
|
||||||
|
"thumbnailCode" TEXT,
|
||||||
"pendingUpload" BOOLEAN NOT NULL DEFAULT true,
|
"pendingUpload" BOOLEAN NOT NULL DEFAULT true,
|
||||||
"uploadedBy" UUID NOT NULL,
|
"uploadedBy" UUID NOT NULL,
|
||||||
"deletedAt" TIMESTAMP(3),
|
"deletedAt" TIMESTAMP(3),
|
||||||
@ -497,6 +498,26 @@ CREATE TABLE "email_system_histories" (
|
|||||||
CONSTRAINT "email_system_histories_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "email_system_histories_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "hero_banner" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"orderPriority" INTEGER,
|
||||||
|
"isClickable" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"title" VARCHAR(225),
|
||||||
|
"tags" TEXT[],
|
||||||
|
"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")
|
||||||
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "system_preferences" (
|
CREATE TABLE "system_preferences" (
|
||||||
"id" UUID NOT NULL,
|
"id" UUID NOT NULL,
|
||||||
@ -629,7 +650,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");
|
CREATE UNIQUE INDEX "episodes_mediaId_episode_key" ON "episodes"("mediaId", "episode");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "videos_serviceId_code_key" ON "videos"("serviceId", "code");
|
CREATE UNIQUE INDEX "videos_serviceId_videoCode_key" ON "videos"("serviceId", "videoCode");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "video_services_name_key" ON "video_services"("name");
|
CREATE UNIQUE INDEX "video_services_name_key" ON "video_services"("name");
|
||||||
@ -664,6 +685,12 @@ CREATE UNIQUE INDEX "email_system_accounts_email_key" ON "email_system_accounts"
|
|||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "email_system_accounts_username_key" ON "email_system_accounts"("username");
|
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
|
-- CreateIndex
|
||||||
CREATE INDEX "_MediaStudios_B_index" ON "_MediaStudios"("B");
|
CREATE INDEX "_MediaStudios_B_index" ON "_MediaStudios"("B");
|
||||||
|
|
||||||
@ -820,6 +847,9 @@ ALTER TABLE "email_system_accounts" ADD CONSTRAINT "email_system_accounts_create
|
|||||||
-- AddForeignKey
|
-- 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;
|
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
|
-- AddForeignKey
|
||||||
ALTER TABLE "system_notifications" ADD CONSTRAINT "system_notifications_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "system_notifications" ADD CONSTRAINT "system_notifications_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "videos" ADD COLUMN "thumbnailCode" TEXT;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
-- 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;
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
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;
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "hero_banner" ADD COLUMN "tags" TEXT[];
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `buttonContent` on the `hero_banner` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `buttonLink` on the `hero_banner` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `description` on the `hero_banner` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `isClickable` on the `hero_banner` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `tags` on the `hero_banner` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `title` on the `hero_banner` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `mediaId` to the `hero_banner` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "hero_banner" DROP COLUMN "buttonContent",
|
||||||
|
DROP COLUMN "buttonLink",
|
||||||
|
DROP COLUMN "description",
|
||||||
|
DROP COLUMN "isClickable",
|
||||||
|
DROP COLUMN "tags",
|
||||||
|
DROP COLUMN "title",
|
||||||
|
ADD COLUMN "mediaId" UUID NOT NULL;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "hero_banner" ADD CONSTRAINT "hero_banner_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "medias"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@ -49,6 +49,7 @@ model Media {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
|
|
||||||
|
bannerPromotion HeroBanner[] @relation("MediaBannerPromotion")
|
||||||
logs MediaLog[] @relation("MediaLogs")
|
logs MediaLog[] @relation("MediaLogs")
|
||||||
episodes Episode[] @relation("MediaEpisodes")
|
episodes Episode[] @relation("MediaEpisodes")
|
||||||
collections Collection[] @relation("MediaCollections")
|
collections Collection[] @relation("MediaCollections")
|
||||||
@ -557,12 +558,8 @@ model EmailSystemHistory {
|
|||||||
model HeroBanner {
|
model HeroBanner {
|
||||||
id String @id @db.Uuid
|
id String @id @db.Uuid
|
||||||
orderPriority Int? @unique
|
orderPriority Int? @unique
|
||||||
isClickable Boolean @default(false)
|
mediaId String @db.Uuid
|
||||||
title String? @db.VarChar(225)
|
media Media @relation("MediaBannerPromotion", fields: [mediaId], references: [id])
|
||||||
tags String[]
|
|
||||||
description String? @db.Text
|
|
||||||
buttonContent String? @db.VarChar(100)
|
|
||||||
buttonLink String? @db.Text
|
|
||||||
imageUrl String? @db.Text
|
imageUrl String? @db.Text
|
||||||
startDate DateTime
|
startDate DateTime
|
||||||
endDate DateTime
|
endDate DateTime
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { Context } from "elysia";
|
|
||||||
import { UserHeaderInformation } from "./types";
|
import { UserHeaderInformation } from "./types";
|
||||||
|
|
||||||
export interface ClientInfoHeader {
|
export interface ClientInfoHeader {
|
||||||
@ -10,25 +9,14 @@ export interface ClientInfoHeader {
|
|||||||
ip: string;
|
ip: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUserHeaderInformation = (
|
export const getUserHeaderInformation = (clientInfo: string): UserHeaderInformation => {
|
||||||
ctx: Context,
|
const clientInfoHeader = (JSON.parse(clientInfo) as ClientInfoHeader) ?? ("unknown" as string);
|
||||||
): UserHeaderInformation => {
|
|
||||||
const clientInfoHeader =
|
|
||||||
(JSON.parse(
|
|
||||||
ctx.request.headers.get("x-client-info") as string,
|
|
||||||
) as ClientInfoHeader) ?? ("unknown" as string);
|
|
||||||
|
|
||||||
const userHeaderInformation = {
|
const userHeaderInformation = {
|
||||||
ip: clientInfoHeader.ip ?? "unknown",
|
ip: clientInfoHeader.ip ?? "unknown",
|
||||||
deviceType: clientInfoHeader.deviceType ?? "unknown",
|
deviceType: clientInfoHeader.deviceType ?? "unknown",
|
||||||
deviceOS:
|
deviceOS: (clientInfoHeader.os ?? "unknown") + " " + (clientInfoHeader.osVersion ?? "unknown"),
|
||||||
(clientInfoHeader.os ?? "unknown") +
|
browser: (clientInfoHeader.browser ?? "unknown") + " " + (clientInfoHeader.browserVersion ?? "unknown"),
|
||||||
" " +
|
|
||||||
(clientInfoHeader.osVersion ?? "unknown"),
|
|
||||||
browser:
|
|
||||||
(clientInfoHeader.browser ?? "unknown") +
|
|
||||||
" " +
|
|
||||||
(clientInfoHeader.browserVersion ?? "unknown"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return userHeaderInformation;
|
return userHeaderInformation;
|
||||||
|
|||||||
@ -9,8 +9,8 @@ export const getOauthProvidersController = (ctx: Context) => {
|
|||||||
return returnReadResponse(
|
return returnReadResponse(
|
||||||
ctx.set,
|
ctx.set,
|
||||||
200,
|
200,
|
||||||
"Getting all oauth available list",
|
"Successfully retrieved the list of oauth providers",
|
||||||
oauthProviderServices
|
oauthProviderServices,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return mainErrorHandler(ctx.set, error);
|
return mainErrorHandler(ctx.set, error);
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { Context } from "elysia";
|
import { Context, Static } from "elysia";
|
||||||
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
|
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
|
||||||
import { githubCallbackService } from "../services/http/githubCallback.service";
|
import { githubCallbackService } from "../services/http/githubCallback.service";
|
||||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||||
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
|
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
|
||||||
|
import { githubCallbackSchema } from "../schemas/githubCallback.schema";
|
||||||
|
|
||||||
export const githubCallbackController = async (
|
export const githubCallbackController = async (ctx: {
|
||||||
ctx: Context & { query: { code: string; callbackURI: string } }
|
set: Context["set"];
|
||||||
) => {
|
query: Static<typeof githubCallbackSchema.query>;
|
||||||
|
headers: Static<typeof githubCallbackSchema.headers>;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const userHeaderInfo = getUserHeaderInformation(ctx);
|
const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]);
|
||||||
|
|
||||||
const authToken = await githubCallbackService(ctx.query, userHeaderInfo);
|
const authToken = await githubCallbackService(ctx.query, userHeaderInfo);
|
||||||
return returnWriteResponse(ctx.set, 200, "Authenticated successfully!", {
|
return returnWriteResponse(ctx.set, 200, "Authenticated successfully!", {
|
||||||
|
|||||||
@ -1,21 +1,18 @@
|
|||||||
import { Context } from "elysia";
|
import { Context, Static } from "elysia";
|
||||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||||
import { githubRequestService } from "../services/http/githubRequest.service";
|
import { githubRequestService } from "../services/http/githubRequest.service";
|
||||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||||
|
import { githubRequestSchema } from "../schemas/githubRequest.schema";
|
||||||
|
|
||||||
export const githubRequestController = async (
|
export const githubRequestController = async (ctx: {
|
||||||
ctx: Context & { query: { callback?: string } },
|
set: Context["set"];
|
||||||
) => {
|
query: Static<typeof githubRequestSchema.query>;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const loginUrl = await githubRequestService(ctx.query.callback);
|
const loginUrl = await githubRequestService(ctx.query.callback);
|
||||||
return returnReadResponse(
|
return returnReadResponse(ctx.set, 200, "GitHub login URL created successfully.", {
|
||||||
ctx.set,
|
endpointUrl: loginUrl,
|
||||||
200,
|
});
|
||||||
"Login URL generated successfully",
|
|
||||||
{
|
|
||||||
endpointUrl: loginUrl,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return mainErrorHandler(ctx.set, error);
|
return mainErrorHandler(ctx.set, error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,20 @@
|
|||||||
import { Context } from "elysia";
|
import { Context, Static } from "elysia";
|
||||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||||
import { googleCallbackService } from "../services/http/googleCallback.service";
|
import { googleCallbackService } from "../services/http/googleCallback.service";
|
||||||
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
|
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
|
||||||
|
import { googleCallbackSchema } from "../schemas/googleCallback.schema";
|
||||||
|
|
||||||
export const googleCallbackController = async (
|
export const googleCallbackController = async (ctx: {
|
||||||
ctx: Context & { query: { code: string; state: string; callbackURI: string } }
|
set: Context["set"];
|
||||||
) => {
|
query: Static<typeof googleCallbackSchema.query>;
|
||||||
|
headers: Static<typeof googleCallbackSchema.headers>;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const userHeaderInfo = getUserHeaderInformation(ctx);
|
const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]);
|
||||||
|
|
||||||
const authToken = await googleCallbackService(ctx.query, userHeaderInfo);
|
const authToken = await googleCallbackService(ctx.query, userHeaderInfo);
|
||||||
return returnReadResponse(ctx.set, 200, "Authenticated successfully!", {
|
return returnReadResponse(ctx.set, 200, "Authentication successful!", {
|
||||||
authToken,
|
authToken,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
import { Context } from "elysia";
|
import { Context, Static } from "elysia";
|
||||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||||
import { googleRequestService } from "../services/http/googleRequest.service";
|
import { googleRequestService } from "../services/http/googleRequest.service";
|
||||||
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
|
||||||
|
import { googleRequestSchema } from "../schemas/googleRequest.schema";
|
||||||
|
|
||||||
export const googleRequestController = async (
|
export const googleRequestController = async (ctx: {
|
||||||
ctx: Context & { query: { callback?: string } }
|
set: Context["set"];
|
||||||
) => {
|
query: Static<typeof googleRequestSchema.query>;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const loginUrl = await googleRequestService(ctx.query.callback);
|
const loginUrl = await googleRequestService(ctx.query.callback);
|
||||||
return returnReadResponse(ctx.set, 200, "Google login url created!", {
|
return returnReadResponse(ctx.set, 200, "Google login URL created successfully.", {
|
||||||
endpointUrl: loginUrl,
|
endpointUrl: loginUrl,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -7,13 +7,21 @@ import { getOauthProvidersController } from "./controllers/getOauthProviders.con
|
|||||||
import { getCallbackProviderUrlController } from "./controllers/getCallbackProviderUrl.controller";
|
import { getCallbackProviderUrlController } from "./controllers/getCallbackProviderUrl.controller";
|
||||||
import { tokenValidationController } from "./controllers/tokenValidation.controller";
|
import { tokenValidationController } from "./controllers/tokenValidation.controller";
|
||||||
import { logoutController } from "./controllers/logout.controller";
|
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" })
|
export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] })
|
||||||
.post("/token/validate", tokenValidationController)
|
.post("/token/validate", tokenValidationController, tokenValidationSchema)
|
||||||
.get("/providers", getOauthProvidersController)
|
.get("/providers", getOauthProvidersController, getOauthProvidersSchema)
|
||||||
.get("/providers/:name/callback", getCallbackProviderUrlController)
|
.get("/providers/:name/callback", getCallbackProviderUrlController, getCallbackProviderUrlSchema)
|
||||||
.get("/github", githubRequestController)
|
.get("/google", googleRequestController, googleRequestSchema)
|
||||||
.get("/github/callback", githubCallbackController)
|
.get("/google/callback", googleCallbackController, googleCallbackSchema)
|
||||||
.get("/google", googleRequestController)
|
.get("/github", githubRequestController, githubRequestSchema)
|
||||||
.get("/google/callback", googleCallbackController)
|
.get("/github/callback", githubCallbackController, githubCallbackSchema)
|
||||||
.post("/logout", logoutController);
|
.post("/logout", logoutController, logoutSchema);
|
||||||
|
|||||||
45
src/modules/auth/schemas/getCallbackProviderUrl.schema.ts
Normal file
45
src/modules/auth/schemas/getCallbackProviderUrl.schema.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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;
|
||||||
55
src/modules/auth/schemas/getOauthProviders.schema.ts
Normal file
55
src/modules/auth/schemas/getOauthProviders.schema.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||||
|
|
||||||
|
export const getOauthProvidersSchema = {
|
||||||
|
detail: {
|
||||||
|
summary: "Get all available oauth providers",
|
||||||
|
description:
|
||||||
|
"This endpoint returns a list of all available and active oauth providers that can be used for authentication.",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successfully retrieved the list of oauth providers",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
success: {
|
||||||
|
type: "boolean",
|
||||||
|
example: true,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: "number",
|
||||||
|
example: 200,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: "string",
|
||||||
|
example: "Successfully retrieved the list of oauth providers",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
example: "google",
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: "string",
|
||||||
|
example: "logos:google-icon",
|
||||||
|
},
|
||||||
|
req_endpoint: {
|
||||||
|
type: "string",
|
||||||
|
example: "auth/google",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies AppRouteSchema;
|
||||||
57
src/modules/auth/schemas/githubCallback.schema.ts
Normal file
57
src/modules/auth/schemas/githubCallback.schema.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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;
|
||||||
54
src/modules/auth/schemas/githubRequest.schema.ts
Normal file
54
src/modules/auth/schemas/githubRequest.schema.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
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;
|
||||||
58
src/modules/auth/schemas/googleCallback.schema.ts
Normal file
58
src/modules/auth/schemas/googleCallback.schema.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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;
|
||||||
54
src/modules/auth/schemas/googleRequest.schema.ts
Normal file
54
src/modules/auth/schemas/googleRequest.schema.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
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;
|
||||||
97
src/modules/auth/schemas/logout.schema.ts
Normal file
97
src/modules/auth/schemas/logout.schema.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
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;
|
||||||
108
src/modules/auth/schemas/tokenValidation.schema.ts
Normal file
108
src/modules/auth/schemas/tokenValidation.schema.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { t } from "elysia";
|
||||||
|
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
||||||
|
|
||||||
|
export const tokenValidationSchema = {
|
||||||
|
headers: t.Object({
|
||||||
|
cookie: t.String({ description: "Authentication token in cookie format, e.g., auth_token=your_jwt_token;" }),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Validate authentication JWT token",
|
||||||
|
description:
|
||||||
|
"Validates the provided authentication JWT token with checking its validity and expiration in redis cache, if not exists, it will be checked in the database. If the token is valid, it returns the user information associated with the token. if the token is invalid or expired, it returns an error message.",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Validation successful",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string", description: "Session ID", default: "xxxx-xxxxx-xxxxx-xxxx" },
|
||||||
|
isAuthenticated: {
|
||||||
|
type: "boolean",
|
||||||
|
description: "Indicates if the token is valid and the user is authenticated",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
validUntil: {
|
||||||
|
type: "string",
|
||||||
|
format: "date-time",
|
||||||
|
description: "Expiration date and time of the token",
|
||||||
|
default: "2024-12-31T23:59:59Z",
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string", description: "User ID", default: "user-12345" },
|
||||||
|
name: { type: "string", description: "User's full name", default: "Lena Nouzen" },
|
||||||
|
email: {
|
||||||
|
type: "string",
|
||||||
|
format: "email",
|
||||||
|
description: "User's email address",
|
||||||
|
default: "lena@example.com",
|
||||||
|
},
|
||||||
|
username: { type: "string", description: "User's username", default: "vladilena" },
|
||||||
|
avatar: {
|
||||||
|
type: "string",
|
||||||
|
format: "uri",
|
||||||
|
description: "URL to the user's avatar image",
|
||||||
|
default: "https://example.com/avatar.jpg",
|
||||||
|
},
|
||||||
|
birthDate: {
|
||||||
|
type: "string",
|
||||||
|
format: "date",
|
||||||
|
description: "User's birth date, can be null if not provided",
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
bioProfile: {
|
||||||
|
type: "string",
|
||||||
|
description: "User's bio/profile description, can be null if not provided",
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
preference: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string", description: "Preference ID", default: "pref-12345" },
|
||||||
|
userId: { type: "string", description: "Associated User ID", default: "user-12345" },
|
||||||
|
langPreference: {
|
||||||
|
type: "string",
|
||||||
|
description: "User's language preference, can be null if not provided",
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
adultFiltering: {
|
||||||
|
type: "string",
|
||||||
|
description: "User's adult content filtering setting",
|
||||||
|
default: "strict",
|
||||||
|
},
|
||||||
|
adultAlert: {
|
||||||
|
type: "string",
|
||||||
|
description: "User's adult content alert setting",
|
||||||
|
default: "enabled",
|
||||||
|
},
|
||||||
|
videoQuality: {
|
||||||
|
type: "string",
|
||||||
|
description: "User's preferred video quality setting",
|
||||||
|
default: "1080p",
|
||||||
|
},
|
||||||
|
serviceDefaultId: {
|
||||||
|
type: "string",
|
||||||
|
description: "Default service ID for the user, can be null if not provided",
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
hideContries: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "List of country codes that the user has chosen to hide content from",
|
||||||
|
default: ["US", "CN"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies AppRouteSchema;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
4
src/modules/flushCache/index.ts
Normal file
4
src/modules/flushCache/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import Elysia from "elysia";
|
||||||
|
import { clearHeroBannerController } from "./controllers/clearHeroBanner.controller";
|
||||||
|
|
||||||
|
export const flushCacheModule = new Elysia({ prefix: "/flush-cache" }).put("/hero-banner", clearHeroBannerController);
|
||||||
12
src/modules/flushCache/services/clearHeroBanner.service.ts
Normal file
12
src/modules/flushCache/services/clearHeroBanner.service.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -20,6 +20,25 @@ export const findAllActiveHeroBannerRepository = async () => {
|
|||||||
startDate: "asc",
|
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) {
|
} catch (error) {
|
||||||
throw new AppError(500, "Failed to fetch active hero banners", error);
|
throw new AppError(500, "Failed to fetch active hero banners", error);
|
||||||
|
|||||||
@ -8,26 +8,31 @@ import { findAllActiveHeroBannerRepository } from "../repositories/GET/findAllAc
|
|||||||
export const getActiveHeroBannerService = async () => {
|
export const getActiveHeroBannerService = async () => {
|
||||||
try {
|
try {
|
||||||
// Check if Hero Banner is enabled in system preferences
|
// Check if Hero Banner is enabled in system preferences
|
||||||
const isHeroBannerEnabled = await findSystemPreferenceService(
|
const isHeroBannerEnabled = await findSystemPreferenceService("HERO_BANNER_ENABLED", "boolean");
|
||||||
"HERO_BANNER_ENABLED",
|
if (!isHeroBannerEnabled) throw new AppError(403, "Hero Banner is disabled");
|
||||||
"boolean",
|
|
||||||
);
|
|
||||||
if (!isHeroBannerEnabled)
|
|
||||||
throw new AppError(403, "Hero Banner is disabled");
|
|
||||||
|
|
||||||
// Try to get active banners from Redis cache
|
// Try to get active banners from Redis cache
|
||||||
const cachedBanners = await redis.get(
|
const cachedBanners = await redis.get(`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`);
|
||||||
`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`,
|
|
||||||
);
|
|
||||||
if (cachedBanners) return JSON.parse(cachedBanners);
|
if (cachedBanners) return JSON.parse(cachedBanners);
|
||||||
|
|
||||||
// If not in cache, fetch from database and cache the result
|
// If not in cache, fetch from database and cache the result
|
||||||
const activeBanners = await findAllActiveHeroBannerRepository();
|
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(
|
await redis.set(
|
||||||
`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`,
|
`${redisKey.filter((key) => key.name === "HERO_BANNER")[0].key}`,
|
||||||
JSON.stringify(activeBanners),
|
JSON.stringify(constructedBanners),
|
||||||
);
|
);
|
||||||
return activeBanners;
|
return constructedBanners;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorForwarder(error);
|
ErrorForwarder(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Prisma } from "@prisma/client";
|
|
||||||
import { AppError } from "../../../helpers/error/instances/app";
|
import { AppError } from "../../../helpers/error/instances/app";
|
||||||
import { prisma } from "../../../utils/databases/prisma/connection";
|
import { prisma } from "../../../utils/databases/prisma/connection";
|
||||||
import { generateUUIDv7 } from "../../../helpers/databases/uuidv7";
|
import { generateUUIDv7 } from "../../../helpers/databases/uuidv7";
|
||||||
import { SystemAccountId } from "../../../config/account/system";
|
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 (
|
export const insertHeroBannerRepository = async (payload: Static<typeof createHeroBannerSchema.body>) => {
|
||||||
payload: Omit<Prisma.HeroBannerCreateInput, "id" | "createdBy">,
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
return await prisma.heroBanner.create({
|
return await prisma.heroBanner.create({
|
||||||
data: {
|
data: {
|
||||||
@ -16,6 +16,9 @@ export const insertHeroBannerRepository = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} 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);
|
throw new AppError(500, "Failed to insert hero banner", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,45 +3,18 @@ import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
|
|||||||
|
|
||||||
export const createHeroBannerSchema = {
|
export const createHeroBannerSchema = {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
isClickable: t.Optional(
|
orderPriority: t.Optional(
|
||||||
t.Boolean({
|
t.Number({ description: "The priority order of the hero banner. Lower numbers indicate higher priority." }),
|
||||||
description: "Indicates whether the hero banner is clickable",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
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",
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
|
mediaId: t.String({ description: "The ID of the media associated with the hero banner" }),
|
||||||
imageUrl: t.Optional(
|
imageUrl: t.Optional(
|
||||||
t.String({
|
t.String({
|
||||||
description: "The URL of the image used in the hero banner",
|
description:
|
||||||
|
"The URL of the image used in the hero banner. If not provided, a thumbnail image of the media will be used.",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
startDate: t.String({
|
startDate: t.Date({ description: "The start date for the hero banner in ISO 8601 format" }),
|
||||||
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" }),
|
||||||
}),
|
|
||||||
endDate: t.String({
|
|
||||||
description: "The end date for the hero banner in ISO 8601 format",
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
detail: {
|
detail: {
|
||||||
summary: "Create a new hero banner",
|
summary: "Create a new hero banner",
|
||||||
@ -64,17 +37,16 @@ export const createHeroBannerSchema = {
|
|||||||
"The created hero banner object. This field is returned only if the environment is running in development mode.",
|
"The created hero banner object. This field is returned only if the environment is running in development mode.",
|
||||||
properties: {
|
properties: {
|
||||||
id: { type: "string", description: "The ID of the created hero banner" },
|
id: { type: "string", description: "The ID of the created hero banner" },
|
||||||
isClickable: { type: "boolean", description: "Indicates whether the hero banner is clickable" },
|
orderPriority: {
|
||||||
title: { type: "string", description: "The title of the hero banner" },
|
type: "number",
|
||||||
tags: {
|
description: "The priority order of the hero banner. Lower numbers indicate higher priority.",
|
||||||
type: "array",
|
},
|
||||||
items: { type: "string" },
|
mediaId: { type: "string", description: "The ID of the media associated with the hero banner" },
|
||||||
description: "An array of tags 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.",
|
||||||
},
|
},
|
||||||
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: {
|
startDate: {
|
||||||
type: "string",
|
type: "string",
|
||||||
format: "date-time",
|
format: "date-time",
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
|
import { Static } from "elysia";
|
||||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||||
import { CreateHeroBannerRequestBody } from "../../controllers/createHeroBanner.controller";
|
|
||||||
import { insertHeroBannerRepository } from "../../repositories/insertHeroBanner.repository";
|
import { insertHeroBannerRepository } from "../../repositories/insertHeroBanner.repository";
|
||||||
|
import { createHeroBannerSchema } from "../../schemas/createHeroBanner.schema";
|
||||||
|
|
||||||
export const createHeroBannerService = async (
|
export const createHeroBannerService = async (payload: Static<typeof createHeroBannerSchema.body>) => {
|
||||||
payload: CreateHeroBannerRequestBody,
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
return await insertHeroBannerRepository(payload);
|
return await insertHeroBannerRepository(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user