From ca57ad862c55e7adfbd2ede666098286e6d6c8b8 Mon Sep 17 00:00:00 2001 From: Sargam Poudel Date: Thu, 21 Nov 2024 07:57:16 +0530 Subject: [PATCH 1/3] feat: figure out stuff --- prisma/schema.prisma | 36 ++++++++++---------- src/actions/user/index.ts | 51 +++++++++++++++++++++++++++++ src/app/api/admin/content/route.ts | 13 ++++++-- src/components/AppxVideoPlayer.tsx | 36 ++++++++++++++++++++ src/components/VideoPlayer2.tsx | 22 ++++++++++--- src/components/admin/AddContent.tsx | 29 ++++++++++++++-- src/lib/auth.ts | 19 +++++++---- 7 files changed, 170 insertions(+), 36 deletions(-) create mode 100644 src/components/AppxVideoPlayer.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 66b0c235a..90d2d78a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -87,6 +87,7 @@ model NotionMetadata { model VideoMetadata { id Int @id @default(autoincrement()) contentId Int + appxVideoId String? video_1080p_mp4_1 String? // Link to 1080p mp4 quality video variant 1 video_1080p_mp4_2 String? // Link to 1080p mp4 quality video variant 2 video_1080p_mp4_3 String? // Link to 1080p mp4 quality video variant 3 @@ -138,9 +139,9 @@ model Session { } model User { - id String @id @default(cuid()) + id String @id @default(cuid()) name String? - email String? @unique + email String? @unique token String? sessions Session[] purchases UserPurchases[] @@ -148,19 +149,20 @@ model User { comments Comment[] votes Vote[] discordConnect DiscordConnect? - disableDrm Boolean @default(false) - bunnyProxyEnabled Boolean @default(false) + disableDrm Boolean @default(false) + bunnyProxyEnabled Boolean @default(false) bookmarks Bookmark[] password String? appxUserId String? appxUsername String? + appxAuthToken String? questions Question[] answers Answer[] certificate Certificate[] - upiIds UpiId[] @relation("UserUpiIds") - solanaAddresses SolanaAddress[] @relation("UserSolanaAddresses") - githubUser GitHubLink? @relation("UserGithub") - bounties BountySubmission[] + upiIds UpiId[] @relation("UserUpiIds") + solanaAddresses SolanaAddress[] @relation("UserSolanaAddresses") + githubUser GitHubLink? @relation("UserGithub") + bounties BountySubmission[] } model GitHubLink { @@ -324,20 +326,19 @@ model Event { } model BountySubmission { - id String @id @default(uuid()) - prLink String + id String @id @default(uuid()) + prLink String paymentMethod String - status String @default("pending") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - amount Float @default(0) - userId String - user User @relation(fields: [userId], references: [id]) + status String @default("pending") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + amount Float @default(0) + userId String + user User @relation(fields: [userId], references: [id]) @@unique([userId, prLink]) } - enum VoteType { UPVOTE DOWNVOTE @@ -359,4 +360,3 @@ enum MigrationStatus { MIGRATED MIGRATION_ERROR } - diff --git a/src/actions/user/index.ts b/src/actions/user/index.ts index d85f351e7..09802bdab 100644 --- a/src/actions/user/index.ts +++ b/src/actions/user/index.ts @@ -1,5 +1,8 @@ 'use server'; import db from '@/db'; +import { authOptions } from '@/lib/auth'; +import axios from 'axios'; +import { getServerSession } from 'next-auth'; export const logoutUser = async (email: string, adminPassword: string) => { if (adminPassword !== process.env.ADMIN_SECRET) { @@ -25,3 +28,51 @@ export const logoutUser = async (email: string, adminPassword: string) => { return { message: 'User logged out' }; }; + +type GetAppxAuthTokenResponse = { + name: string | null; + email: string | null; + appxAuthToken: string | null; + appxUserId: string | null; +} + +export const GetAppxAuthToken = async (): Promise => { + const session = await getServerSession(authOptions) + if (!session || !session.user) throw new Error("User is not logged in"); + + const user = await db.user.findFirst({ + where: { + email: session.user.email, + }, + select: { + name: true, + email: true, + appxAuthToken: true, + appxUserId: true + } + }); + + if (!user) throw new Error("User not found"); + return user +} + +export const GetAppxVideoPlayerUrl = async (courseId: string, videoId: string): Promise => { + const { name, email, appxAuthToken, appxUserId } = await GetAppxAuthToken(); + const url = `${process.env.APPX_BASE_API}/get/fetchVideoDetailsById?course_id=${courseId}&video_id=${videoId}&ytflag=${1}&folder_wise_course=${1}`; + const config = { + url, + method: "get", + maxBodyLength: Infinity, + headers: { + Authorization: appxAuthToken, + "Auth-Key": process.env.APPX_AUTH_KEY, + "User-Id": appxUserId, + }, + }; + + const res = await axios.request(config); + const { video_player_token, video_player_url } = res.data.data; + const full_video_url = `${video_player_url}${video_player_token}&watermark=${name}%0A${email}`; + return full_video_url; +} + diff --git a/src/app/api/admin/content/route.ts b/src/app/api/admin/content/route.ts index 4f3ab38fb..28d57d17b 100644 --- a/src/app/api/admin/content/route.ts +++ b/src/app/api/admin/content/route.ts @@ -48,7 +48,7 @@ export const POST = async (req: NextRequest) => { rest, discordChecked, }: { - type: 'video' | 'folder' | 'notion'; + type: 'video' | 'folder' | 'notion' | 'appx'; thumbnail: string; title: string; courseId: number; @@ -110,6 +110,13 @@ export const POST = async (req: NextRequest) => { }, }); } + } else if (type === 'appx') { + await db.videoMetadata.create({ + data: { + appxVideoId: metadata.appxVideoId, + contentId: content.id, + }, + }); } else if (type === 'video') { await db.videoMetadata.create({ data: { @@ -156,7 +163,7 @@ export const POST = async (req: NextRequest) => { }); } } - if (discordChecked && (type === 'notion' || type === 'video')) { + if (discordChecked && (type === 'notion' || type === 'video' || type === 'appx')) { if (!process.env.NEXT_PUBLIC_DISCORD_WEBHOOK_URL) { return NextResponse.json( { message: 'Environment variable for discord webhook is not set' }, @@ -181,7 +188,7 @@ export const POST = async (req: NextRequest) => { return NextResponse.json( { message: - discordChecked && (type === 'notion' || type === 'video') + discordChecked && (type === 'notion' || type === 'video' || type === 'appx') ? 'Content Added and Discord notification has been sent' : 'Content has been added', }, diff --git a/src/components/AppxVideoPlayer.tsx b/src/components/AppxVideoPlayer.tsx new file mode 100644 index 000000000..917265369 --- /dev/null +++ b/src/components/AppxVideoPlayer.tsx @@ -0,0 +1,36 @@ +"use client"; +import { GetAppxVideoPlayerUrl } from "@/actions/user"; +import { useEffect, useState } from "react"; + +export const AppxVideoPlayer = ({ + courseId, + videoId, +}: { + courseId: string; + videoId: string; +}) => { + const [url, setUrl] = useState(""); + + useEffect(() => { + (async () => { + try { + const videoUrl = await GetAppxVideoPlayerUrl(courseId, videoId) + setUrl(videoUrl) + } catch { + if (window === undefined) return; + location.href = '/api/auth/signin'; + } + })(); + }, []) + + if (!url.length) { + return

Loading...

; + } + + return ( + + ); +} diff --git a/src/components/VideoPlayer2.tsx b/src/components/VideoPlayer2.tsx index 6f72e8dd3..7e7151e7f 100644 --- a/src/components/VideoPlayer2.tsx +++ b/src/components/VideoPlayer2.tsx @@ -16,6 +16,7 @@ import { YoutubeRenderer } from './YoutubeRenderer'; import { toast } from 'sonner'; import { createRoot } from 'react-dom/client'; import { PictureInPicture2 } from 'lucide-react'; +import { AppxVideoPlayer } from './AppxVideoPlayer'; // todo correct types interface VideoPlayerProps { @@ -311,7 +312,7 @@ export const VideoPlayer: FunctionComponent = ({ player.playbackRate(1); } }; - document.addEventListener('keydown', handleKeyPress, {capture: true}); + document.addEventListener('keydown', handleKeyPress, { capture: true }); document.addEventListener('keyup', handleKeyUp); // Cleanup function return () => { @@ -471,12 +472,23 @@ export const VideoPlayer: FunctionComponent = ({ return regex.test(url); }; - if (isYoutubeUrl(vidUrl)) { - return ; - } + const isAppxEncryptedVideo = (url: string) => { + return url.startsWith('https://player.akamai.net.in/secure-player'); + }; + + if (isYoutubeUrl(vidUrl)) return ; + + //TODO: Figure out how to get the courseId + if (isAppxEncryptedVideo(vidUrl)) + return ( + + ); return ( -
+
); diff --git a/src/components/admin/AddContent.tsx b/src/components/admin/AddContent.tsx index 1cdd99174..efe9d9438 100644 --- a/src/components/admin/AddContent.tsx +++ b/src/components/admin/AddContent.tsx @@ -46,7 +46,7 @@ export const AddContent = ({ const [loading, setLoading] = useState(false); const getLabelClassName = (value: string) => { - return `flex gap-6 p-6 rounded-lg items-center space-x-2 ${ + return `flex gap-1 p-4 rounded-lg items-center space-x-2 ${ type === value ? 'border-[3px] border-blue-500' : 'border-[3px]' }`; }; @@ -61,6 +61,7 @@ export const AddContent = ({ title, courseId, parentContentId, + //* Metadata will be list of resolutions for normal videos and appxVideoId for appx videos metadata, adminPassword, courseTitle, @@ -88,17 +89,21 @@ export const AddContent = ({ return (
-