Compare commits
4 Commits
96b5101742
...
aea16ad35a
| Author | SHA1 | Date | |
|---|---|---|---|
| aea16ad35a | |||
| 6215e14420 | |||
| cfb9b61c8a | |||
| e92d996621 |
24
.env.example
24
.env.example
@ -1,28 +1,40 @@
|
|||||||
APP_NAME=NounozCommunity
|
# Environment variables for application
|
||||||
APP_ENV=development
|
APP_NAME=AstofoTV
|
||||||
|
APP_ENV=development # Change to "production" when deploying to production environment, this will disable some development features and enable production optimizations.
|
||||||
APP_DOMAIN=
|
APP_DOMAIN=
|
||||||
APP_PROTOCOL=
|
APP_PROTOCOL=
|
||||||
APP_PORT=
|
APP_PORT=
|
||||||
API_KEY=87e20de621fe18930dfbe714d8684bd5ada376903c3092fa3b9aa4a2db10cfba
|
APP_URL=
|
||||||
|
API_KEY=
|
||||||
ALLOWED_ORIGINS=www.nounoz.com,nounoz.com,localhost
|
ALLOWED_ORIGINS=www.nounoz.com,nounoz.com,localhost
|
||||||
|
|
||||||
|
# Admin user configuration
|
||||||
DEFAULT_ADMIN_EMAIL=
|
DEFAULT_ADMIN_EMAIL=
|
||||||
DEFAULT_ADMIN_USERNAME=
|
DEFAULT_ADMIN_USERNAME=
|
||||||
DEFAULT_ADMIN_PASSWORD=
|
DEFAULT_ADMIN_PASSWORD=
|
||||||
|
|
||||||
|
# Application features
|
||||||
|
ENABLE_REGISTRATION=
|
||||||
|
ENABLE_HERO_BANNER=
|
||||||
|
|
||||||
|
# Auth Service configuration
|
||||||
SALT_ROUNDS=
|
SALT_ROUNDS=
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
SESSION_EXPIRE=
|
SESSION_EXPIRE=
|
||||||
|
SESSION_CACHE_EXPIRE=
|
||||||
|
|
||||||
|
# MinIO configuration
|
||||||
MINIO_HOST=
|
MINIO_HOST=
|
||||||
MINIO_PORT=
|
MINIO_PORT=
|
||||||
MINIO_ACCESS_KEY=
|
MINIO_ACCESS_KEY=
|
||||||
MINIO_SECRET_KEY=
|
MINIO_SECRET_KEY=
|
||||||
MINIO_BUCKET=
|
MINIO_BUCKET=
|
||||||
|
|
||||||
|
# MyAnimeList API credentials
|
||||||
MAL_CLIENT_ID=
|
MAL_CLIENT_ID=
|
||||||
MAL_CLIENT_SECRET=
|
MAL_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# SMTP configuration
|
||||||
SMTP_HOST=
|
SMTP_HOST=
|
||||||
SMTP_PORT=
|
SMTP_PORT=
|
||||||
SMTP_SECURE=
|
SMTP_SECURE=
|
||||||
@ -30,14 +42,18 @@ SMTP_USER=
|
|||||||
SMTP_PASS=
|
SMTP_PASS=
|
||||||
SMTP_FROM=
|
SMTP_FROM=
|
||||||
|
|
||||||
|
# Caching configuration
|
||||||
REDIS_HOST=localhost
|
REDIS_HOST=localhost
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# Database configuration
|
||||||
DATABASE_URL=
|
DATABASE_URL=
|
||||||
|
SHADOW_DATABASE_URL=
|
||||||
|
ENABLE_PRISMA_LOG=
|
||||||
|
|
||||||
|
# OAuth configuration
|
||||||
GITHUB_CLIENT_ID=
|
GITHUB_CLIENT_ID=
|
||||||
GITHUB_CLIENT_SECRET=
|
GITHUB_CLIENT_SECRET=
|
||||||
|
|
||||||
GOOGLE_CLIENT_ID=
|
GOOGLE_CLIENT_ID=
|
||||||
GOOGLE_CLIENT_SECRET=
|
GOOGLE_CLIENT_SECRET=
|
||||||
@ -2,15 +2,7 @@ import fs from "fs";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
// These keys will not be cleared in the .env.example file
|
// These keys will not be cleared in the .env.example file
|
||||||
const PRESERVED_KEYS = [
|
const PRESERVED_KEYS = ["APP_NAME", "APP_ENV", "PORT", "ALLOWED_ORIGINS", "REDIS_HOST", "REDIS_PORT"];
|
||||||
"APP_NAME",
|
|
||||||
"APP_ENV",
|
|
||||||
"PORT",
|
|
||||||
"API_KEY",
|
|
||||||
"ALLOWED_ORIGINS",
|
|
||||||
"REDIS_HOST",
|
|
||||||
"REDIS_PORT",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Script to create or update the .env.example file based on the .env file.
|
* Script to create or update the .env.example file based on the .env file.
|
||||||
|
|||||||
@ -16,8 +16,8 @@ interface GithubUserData {
|
|||||||
login: string;
|
login: string;
|
||||||
id: number;
|
id: number;
|
||||||
node_id: string;
|
node_id: string;
|
||||||
avatar_url: string;
|
avatar_url?: string;
|
||||||
gravatar_id: string;
|
gravatar_id?: string;
|
||||||
url: string;
|
url: string;
|
||||||
html_url: string;
|
html_url: string;
|
||||||
followers_url: string;
|
followers_url: string;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { email } from "zod";
|
||||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||||
import { UserHeaderInformation } from "../../../../helpers/http/userHeader/getUserHeaderInformation/types";
|
import { UserHeaderInformation } from "../../../../helpers/http/userHeader/getUserHeaderInformation/types";
|
||||||
import { GithubCallbackUserData } from "../../auth.types";
|
import { GithubCallbackUserData } from "../../auth.types";
|
||||||
@ -6,7 +7,7 @@ import { OAuthUserProvisionService } from "../internal/OAuthUserProvision.servic
|
|||||||
|
|
||||||
export const githubCallbackService = async (
|
export const githubCallbackService = async (
|
||||||
query: { code: string; callbackURI: string },
|
query: { code: string; callbackURI: string },
|
||||||
userHeaderInfo: UserHeaderInformation
|
userHeaderInfo: UserHeaderInformation,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
// Initialize GitHub provider
|
// Initialize GitHub provider
|
||||||
@ -37,21 +38,18 @@ export const githubCallbackService = async (
|
|||||||
// Provision or authenticate the user in the system
|
// Provision or authenticate the user in the system
|
||||||
return await OAuthUserProvisionService(
|
return await OAuthUserProvisionService(
|
||||||
{
|
{
|
||||||
provider: "github",
|
fullname: userPayload.user_data.name || userPayload.user_data.login,
|
||||||
providerId: userPayload.user_data.id.toString(),
|
username: `gh_${userPayload.user_data.id}`,
|
||||||
providerToken: accessToken,
|
email: userPayload.user_email.find((email) => email.primary)?.email || userPayload.user_email[0]?.email,
|
||||||
providerPayload: userPayload,
|
|
||||||
email:
|
|
||||||
userPayload.user_email.find((email) => email.primary === true)
|
|
||||||
?.email || userPayload.user_email[0].email,
|
|
||||||
username: `git_${userPayload.user_data.id}`,
|
|
||||||
name: userPayload.user_data.name ?? userPayload.user_data.login,
|
|
||||||
avatar: userPayload.user_data.avatar_url,
|
avatar: userPayload.user_data.avatar_url,
|
||||||
password: Math.random()
|
bio: userPayload.user_data.bio || undefined,
|
||||||
.toString(36)
|
oauthProvider: {
|
||||||
.slice(2, 16),
|
providerName: "github",
|
||||||
|
sub: userPayload.user_data.id.toString(),
|
||||||
|
token: accessToken,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
userHeaderInfo
|
userHeaderInfo,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorForwarder(error, 500, "Authentication service error");
|
ErrorForwarder(error, 500, "Authentication service error");
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { UserHeaderInformation } from "../../../../helpers/http/userHeader/getUs
|
|||||||
import { createUserSessionService } from "../../../userSession/services/createUserSession.service";
|
import { createUserSessionService } from "../../../userSession/services/createUserSession.service";
|
||||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||||
import { createUserViaOauth } from "../../../user/user.types";
|
import { createUserViaOauth } from "../../../user/user.types";
|
||||||
import { createUserService } from "../../../user/services/internal/createUser.service";
|
|
||||||
import { AppError } from "../../../../helpers/error/instances/app";
|
import { AppError } from "../../../../helpers/error/instances/app";
|
||||||
import { findAuthIdentityByEmailAndProviderRepository } from "../../repositories/READ/findAuthIdentityByEmailAndProvider.repository";
|
import { findAuthIdentityByEmailAndProviderRepository } from "../../repositories/READ/findAuthIdentityByEmailAndProvider.repository";
|
||||||
import { createUserWithOAuthCredentialsRepository } from "../../repositories/WRITE/createUserWithOAuthCredentials.repository";
|
import { createUserWithOAuthCredentialsRepository } from "../../repositories/WRITE/createUserWithOAuthCredentials.repository";
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
import { AppError } from "../../../../helpers/error/instances/app";
|
|
||||||
import { prisma } from "../../../../utils/databases/prisma/connection";
|
|
||||||
|
|
||||||
export const findAllActiveHeroBannerRepository = async (userId?: string) => {
|
|
||||||
try {
|
|
||||||
return await prisma.heroBanner.findMany({
|
|
||||||
where: {
|
|
||||||
startDate: {
|
|
||||||
lte: new Date(),
|
|
||||||
},
|
|
||||||
endDate: {
|
|
||||||
gte: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
orderPriority: "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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_count: {
|
|
||||||
select: {
|
|
||||||
inCollections: {
|
|
||||||
where: {
|
|
||||||
collection: {
|
|
||||||
ownerId: userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new AppError(500, "Failed to fetch active hero banners", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { AppError } from "../../../../helpers/error/instances/app";
|
||||||
|
import { prisma } from "../../../../utils/databases/prisma/connection";
|
||||||
|
|
||||||
|
export const showHeroBannerToHomePageRepository = async () => {
|
||||||
|
try {
|
||||||
|
return await prisma.homeMediaBanner.findMany({
|
||||||
|
where: {
|
||||||
|
start_show: {
|
||||||
|
lte: new Date(),
|
||||||
|
},
|
||||||
|
end_show: {
|
||||||
|
gte: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
priority: "asc",
|
||||||
|
created_at: "desc",
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
media: {
|
||||||
|
select: {
|
||||||
|
title: true,
|
||||||
|
synopsis: true,
|
||||||
|
large_image_url: true,
|
||||||
|
genres: {
|
||||||
|
select: {
|
||||||
|
genre: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
slug: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new AppError(500, "Error fetching hero banner data", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,44 +1,14 @@
|
|||||||
import { AppError } from "../../../helpers/error/instances/app";
|
import { AppError } from "../../../helpers/error/instances/app";
|
||||||
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
|
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
|
||||||
import { tokenValidationService } from "../../auth/services/http/tokenValidation.service";
|
import { showHeroBannerToHomePageRepository } from "../repositories/READ/showHeroBannerToHomePage.repository";
|
||||||
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 ({ cookie }: { cookie?: string }) => {
|
||||||
try {
|
try {
|
||||||
const userId = cookie ? (await tokenValidationService(cookie)).user.id : undefined;
|
const isHeroBannerEnabled = process.env.ENABLE_HERO_BANNER === "true";
|
||||||
|
|
||||||
// Check if Hero Banner is enabled in system preferences
|
if (!isHeroBannerEnabled) throw new AppError(403, "Hero banner feature 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.
|
return await showHeroBannerToHomePageRepository();
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorForwarder(error);
|
ErrorForwarder(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import Elysia, { Context } from "elysia";
|
|
||||||
import { returnWriteResponse } from "../../helpers/callback/httpResponse";
|
|
||||||
|
|
||||||
export const systemPreferenceModule = new Elysia({
|
|
||||||
prefix: "/system-preference",
|
|
||||||
}).get("/", (ctx: Context) => {
|
|
||||||
return returnWriteResponse(
|
|
||||||
ctx.set,
|
|
||||||
200,
|
|
||||||
"System Preference module is up and running",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { AppError } from "../../../../helpers/error/instances/app";
|
|
||||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
|
||||||
import { redis } from "../../../../utils/databases/redis/connection";
|
|
||||||
import { findSystemPreferenceRepository } from "../repositories/findSystemPreference.repository";
|
|
||||||
|
|
||||||
export const findSystemPreferenceService = async (
|
|
||||||
key: string,
|
|
||||||
type: "boolean" | "string" | "number" = "string",
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
// First, check if the system preference is exists in redis cache
|
|
||||||
const cachedValue = await redis.get(
|
|
||||||
`${process.env.APP_NAME}:configs:${key}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!cachedValue) {
|
|
||||||
// If not exists in cache, fetch from database. If found, return the value and set it to cache, if not found, throw an error
|
|
||||||
const systemPreference = await findSystemPreferenceRepository(key);
|
|
||||||
if (!systemPreference)
|
|
||||||
throw new AppError(404, "System preference not found");
|
|
||||||
|
|
||||||
// and set it to cache for future requests
|
|
||||||
await redis.set(
|
|
||||||
`${process.env.APP_NAME}:configs:${key}`,
|
|
||||||
systemPreference.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Return the value from database
|
|
||||||
return parseValue(systemPreference.value, type);
|
|
||||||
} else {
|
|
||||||
return parseValue(cachedValue, type);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ErrorForwarder(error, 500, "Failed to find system preference");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseValue = (value: string, type: "boolean" | "string" | "number") => {
|
|
||||||
switch (type) {
|
|
||||||
case "boolean":
|
|
||||||
return value === "true";
|
|
||||||
case "number":
|
|
||||||
return Number(value);
|
|
||||||
default:
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import { AppError } from "../../../../helpers/error/instances/app";
|
|
||||||
import { prisma } from "../../../../utils/databases/prisma/connection";
|
|
||||||
|
|
||||||
export const findSystemPreferenceRepository = async (key: string) => {
|
|
||||||
try {
|
|
||||||
return await prisma.systemPreference.findUnique({
|
|
||||||
where: {
|
|
||||||
key,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new AppError(500, "Failed to find system preference", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -8,8 +8,7 @@ export const deleteUserSessionRepository = async (sessionId: string) => {
|
|||||||
id: sessionId,
|
id: sessionId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
isAuthenticated: false,
|
logout_at: new Date(),
|
||||||
deletedAt: new Date(),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user