working site
This commit is contained in:
		
							parent
							
								
									08d1278f11
								
							
						
					
					
						commit
						bcdceb615c
					
				
							
								
								
									
										7
									
								
								docker/.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docker/.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | node_modules | ||||||
|  | .next | ||||||
|  | Dockerfile | ||||||
|  | .dockerignore | ||||||
|  | .env | ||||||
|  | .env.local | ||||||
|  | .env.development.local | ||||||
							
								
								
									
										40
									
								
								docker/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								docker/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | # 1. Install dependencies only when needed | ||||||
|  | FROM node:18-alpine AS deps | ||||||
|  | WORKDIR /app | ||||||
|  | 
 | ||||||
|  | # Install Prisma Client (important for runtime) | ||||||
|  | COPY package.json package-lock.json ./ | ||||||
|  | RUN npm ci | ||||||
|  | 
 | ||||||
|  | # 2. Build the app with Prisma & Next.js | ||||||
|  | FROM node:18-alpine AS builder | ||||||
|  | WORKDIR /app | ||||||
|  | 
 | ||||||
|  | COPY . . | ||||||
|  | COPY --from=deps /app/node_modules ./node_modules | ||||||
|  | 
 | ||||||
|  | # Generate Prisma client | ||||||
|  | RUN npx prisma generate | ||||||
|  | 
 | ||||||
|  | # Build the app | ||||||
|  | RUN npm run build | ||||||
|  | 
 | ||||||
|  | # 3. Final image | ||||||
|  | FROM node:18-alpine AS runner | ||||||
|  | WORKDIR /app | ||||||
|  | 
 | ||||||
|  | ENV NODE_ENV=production | ||||||
|  | 
 | ||||||
|  | # Copy the built app + node_modules | ||||||
|  | COPY --from=builder /app/public ./public | ||||||
|  | COPY --from=builder /app/.next ./.next | ||||||
|  | COPY --from=builder /app/node_modules ./node_modules | ||||||
|  | COPY --from=builder /app/package.json ./package.json | ||||||
|  | COPY --from=builder /app/prisma ./prisma | ||||||
|  | 
 | ||||||
|  | # Include the Prisma client | ||||||
|  | RUN npx prisma generate | ||||||
|  | 
 | ||||||
|  | # Expose port and run | ||||||
|  | EXPOSE 3000 | ||||||
|  | CMD ["npm", "start"] | ||||||
							
								
								
									
										20
									
								
								docker/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								docker/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | version: "3.8" | ||||||
|  | 
 | ||||||
|  | services: | ||||||
|  |   app: | ||||||
|  |     build: | ||||||
|  |       context: . | ||||||
|  |       dockerfile: Dockerfile | ||||||
|  |     ports: | ||||||
|  |       - "3000:3000" | ||||||
|  |     environment: | ||||||
|  |       DATABASE_URL: "mysql://filehost:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6@10.0.0.1:3306/file_hosting_db" | ||||||
|  |       AUTH_SECRET: "lHpKepMT0dyBHcFQzeN9B5e4Rn/DG6Lc5aiMIKa9HdY=" | ||||||
|  |       AUTH_DISCORD_ID: "1360729915678392492" | ||||||
|  |       AUTH_DISCORD_SECRET: "lIrkEwb2PpMpLZM7Yb9pGVeT7YLgIC_C" | ||||||
|  |       AUTH_TRUST_HOST: "true" | ||||||
|  |       SKIP_ENV_VALIDATION: "true" | ||||||
|  |     volumes: | ||||||
|  |       - type: bind | ||||||
|  |         source: /mnt/0TB/DATA/AppData/file-hosting/uploads | ||||||
|  |         target: /uploads | ||||||
| @ -6,7 +6,19 @@ import "./src/env.js"; | |||||||
| 
 | 
 | ||||||
| const nextConfig = { | const nextConfig = { | ||||||
|   reactStrictMode: true, |   reactStrictMode: true, | ||||||
|   experimental: {}, |   eslint: { | ||||||
|  |     ignoreDuringBuilds: true, | ||||||
|  |   }, | ||||||
|  |   images: { | ||||||
|  |     remotePatterns: [ | ||||||
|  |       { | ||||||
|  |         protocol: "https", | ||||||
|  |         hostname: "cdn.discordapp.com", | ||||||
|  |         port: "", | ||||||
|  |         pathname: "/**", | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default nextConfig; | export default nextConfig; | ||||||
|  | |||||||
| @ -1,3 +1,6 @@ | |||||||
|  | //!eslint-disable @typescript-eslint/no-unsafe-assignment
 | ||||||
|  | //!eslint-disable @typescript-eslint/no-unsafe-argument
 | ||||||
|  | 
 | ||||||
| "use client"; | "use client"; | ||||||
| 
 | 
 | ||||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| @ -17,8 +20,7 @@ interface FileGridProps { | |||||||
| export default function FileGrid({ session }: FileGridProps) { | export default function FileGrid({ session }: FileGridProps) { | ||||||
|   const [files, setFiles] = useState<File[]>([]); |   const [files, setFiles] = useState<File[]>([]); | ||||||
|   const [error, setError] = useState<string | null>(null); |   const [error, setError] = useState<string | null>(null); | ||||||
| 
 |   const router = useRouter(); | ||||||
|    |  | ||||||
| 
 | 
 | ||||||
|   const fetchFiles = async () => { |   const fetchFiles = async () => { | ||||||
|     try { |     try { | ||||||
| @ -26,7 +28,7 @@ export default function FileGrid({ session }: FileGridProps) { | |||||||
|       if (!response.ok) { |       if (!response.ok) { | ||||||
|         throw new Error("Failed to fetch files"); |         throw new Error("Failed to fetch files"); | ||||||
|       } |       } | ||||||
|       const data: { files: File[] } = await response.json(); |       const data = await response.json() as { files: File[] }; // Explicitly type the response
 | ||||||
|       setFiles(data.files); |       setFiles(data.files); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       console.error(err); |       console.error(err); | ||||||
| @ -71,12 +73,14 @@ export default function FileGrid({ session }: FileGridProps) { | |||||||
|     const eventSource = new EventSource("/api/files/stream"); |     const eventSource = new EventSource("/api/files/stream"); | ||||||
| 
 | 
 | ||||||
|     eventSource.onmessage = (event) => { |     eventSource.onmessage = (event) => { | ||||||
|       const data = JSON.parse(event.data); |       const data: { type: string; file?: File; fileId?: string } = JSON.parse(event.data); // Explicitly type the parsed data
 | ||||||
| 
 | 
 | ||||||
|       if (data.type === "file-added") { |       if (data.type === "file-added" && data.file) { | ||||||
|         setFiles((prevFiles) => [...prevFiles, data.file]); |         if (data.file) { | ||||||
|  |           setFiles((prevFiles) => [...prevFiles, data.file as File]); | ||||||
|  |         } | ||||||
|         toast.success(`File "${data.file.name}" added!`); |         toast.success(`File "${data.file.name}" added!`); | ||||||
|       } else if (data.type === "file-removed") { |       } else if (data.type === "file-removed" && data.fileId) { | ||||||
|         setFiles((prevFiles) => prevFiles.filter((file) => file.id !== data.fileId)); |         setFiles((prevFiles) => prevFiles.filter((file) => file.id !== data.fileId)); | ||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| @ -123,7 +127,6 @@ export default function FileGrid({ session }: FileGridProps) { | |||||||
|   if (error) { |   if (error) { | ||||||
|     return <div className="text-red-500">{error}</div>; |     return <div className="text-red-500">{error}</div>; | ||||||
|   } |   } | ||||||
|   const router = useRouter(); |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|       <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 md:gap-8"> |       <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 md:gap-8"> | ||||||
|  | |||||||
| @ -40,7 +40,7 @@ export default function UploadForm() { | |||||||
| 
 | 
 | ||||||
|       xhr.onload = () => { |       xhr.onload = () => { | ||||||
|         if (xhr.status === 200) { |         if (xhr.status === 200) { | ||||||
|           const response = JSON.parse(xhr.responseText); |           const response: { url: string } = JSON.parse(xhr.responseText); // Explicitly type the response
 | ||||||
|           setUploadedFileUrl(response.url); // Assume the API returns the uploaded file URL
 |           setUploadedFileUrl(response.url); // Assume the API returns the uploaded file URL
 | ||||||
|           toast.success("File uploaded successfully!"); |           toast.success("File uploaded successfully!"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,20 +4,20 @@ import { promises as fs } from "fs"; | |||||||
| 
 | 
 | ||||||
| export async function GET(req: Request) { | export async function GET(req: Request) { | ||||||
|   const url = new URL(req.url); |   const url = new URL(req.url); | ||||||
|   const fileName = url.searchParams.get("fileName"); |   const fileId = url.searchParams.get("fileName"); | ||||||
| 
 | 
 | ||||||
|   if (!fileName) { |   if (!fileId) { | ||||||
|     return NextResponse.json({ error: "File name is required" }, { status: 400 }); |     return NextResponse.json({ error: "File id is required" }, { status: 400 }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   try { |   try { | ||||||
|     const filePath = path.join(process.cwd(), "uploads", fileName); |     const filePath = path.join(process.cwd(), "uploads", fileId); | ||||||
|     const fileBuffer = await fs.readFile(filePath); |     const fileBuffer = await fs.readFile(filePath); | ||||||
| 
 | 
 | ||||||
|     return new Response(fileBuffer, { |     return new Response(fileBuffer, { | ||||||
|       headers: { |       headers: { | ||||||
|         "Content-Type": "application/octet-stream", |         "Content-Type": "application/octet-stream", | ||||||
|         "Content-Disposition": `attachment; filename="${fileName}"`, |         "Content-Disposition": `attachment; filename="${fileId}"`, | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|  | |||||||
| @ -1,6 +1,4 @@ | |||||||
| import { NextResponse } from "next/server"; | import { clients } from "~/utils/notifyClients"; | ||||||
| 
 |  | ||||||
| const clients: Set<any> = new Set(); |  | ||||||
| 
 | 
 | ||||||
| export async function GET() { | export async function GET() { | ||||||
|   const stream = new ReadableStream({ |   const stream = new ReadableStream({ | ||||||
| @ -20,16 +18,9 @@ export async function GET() { | |||||||
| 
 | 
 | ||||||
|       clients.add(client); |       clients.add(client); | ||||||
| 
 | 
 | ||||||
|       // Remove the client when the stream is closed
 |  | ||||||
|       const abortListener = () => { |  | ||||||
|         clients.delete(client); |  | ||||||
|         controller.close(); // Ensure the stream is closed when the client disconnects
 |  | ||||||
|       }; |  | ||||||
|       signal.addEventListener("abort", abortListener); |  | ||||||
| 
 |  | ||||||
|       // Cleanup the abort listener when the stream is closed
 |  | ||||||
|       signal.addEventListener("abort", () => { |       signal.addEventListener("abort", () => { | ||||||
|         signal.removeEventListener("abort", abortListener); |         clients.delete(client); | ||||||
|  |         controller.close(); | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
| @ -42,15 +33,3 @@ export async function GET() { | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // Notify all connected clients about a file change
 |  | ||||||
| export function notifyClients(data: any) { |  | ||||||
|   const message = JSON.stringify(data); |  | ||||||
|   clients.forEach((client) => { |  | ||||||
|     try { |  | ||||||
|       client.send(message); |  | ||||||
|     } catch (error) { |  | ||||||
|       console.error("Failed to send message to a client:", error); |  | ||||||
|     } |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| @ -3,7 +3,7 @@ import { db } from "~/server/db"; | |||||||
| import { auth } from "~/server/auth"; | import { auth } from "~/server/auth"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import { promises as fs } from "fs"; | import { promises as fs } from "fs"; | ||||||
| import { notifyClients } from "../files/stream/route"; | import { notifyClients } from "~/utils/notifyClients"; | ||||||
| 
 | 
 | ||||||
| export async function DELETE(req: Request) { | export async function DELETE(req: Request) { | ||||||
|   const session = await auth(); |   const session = await auth(); | ||||||
| @ -13,15 +13,13 @@ export async function DELETE(req: Request) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   try { |   try { | ||||||
|     const body = await req.json().catch(() => null); // Handle empty or invalid JSON
 |     const body = (await req.json()) as { id: string } | null; | ||||||
|     if (!body || !body.id) { |     if (!body?.id) { | ||||||
|       return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); |       return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const resourceId = body.id; |  | ||||||
| 
 |  | ||||||
|     const resource = await db.file.findUnique({ |     const resource = await db.file.findUnique({ | ||||||
|       where: { id: resourceId }, |       where: { id: body.id }, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     if (!resource || resource.uploadedById !== session.user.id) { |     if (!resource || resource.uploadedById !== session.user.id) { | ||||||
| @ -34,11 +32,10 @@ export async function DELETE(req: Request) { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     await db.file.delete({ |     await db.file.delete({ | ||||||
|       where: { id: resourceId }, |       where: { id: body.id }, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Notify clients about the deleted file
 |     notifyClients({ type: "file-removed", fileId: body.id }); | ||||||
|     notifyClients({ type: "file-removed", fileId: resourceId }); |  | ||||||
| 
 | 
 | ||||||
|     return NextResponse.json({ message: "Resource deleted successfully" }); |     return NextResponse.json({ message: "Resource deleted successfully" }); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|  | |||||||
| @ -2,12 +2,10 @@ import { NextResponse } from "next/server"; | |||||||
| import { db } from "~/server/db"; | import { db } from "~/server/db"; | ||||||
| import { auth } from "~/server/auth"; | import { auth } from "~/server/auth"; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| export async function GET(req: Request) { | export async function GET(req: Request) { | ||||||
|   const session = await auth(); |   const session = await auth(); | ||||||
|   const url = new URL(req.url); |   const url = new URL(req.url); | ||||||
|   const fileName = url.searchParams.get("file"); |   const fileName = url.searchParams.get("id"); | ||||||
| 
 | 
 | ||||||
|   if (!fileName) { |   if (!fileName) { | ||||||
|     return NextResponse.json({ error: "File name is required" }, { status: 400 }); |     return NextResponse.json({ error: "File name is required" }, { status: 400 }); | ||||||
| @ -26,12 +24,12 @@ export async function GET(req: Request) { | |||||||
|     return NextResponse.json({ |     return NextResponse.json({ | ||||||
|       name: file.name, |       name: file.name, | ||||||
|       size: file.size, |       size: file.size, | ||||||
|       owner: file.uploadedBy?.name || "Unknown", |       owner: file.uploadedBy?.name ?? "Unknown", // Use nullish coalescing
 | ||||||
|       uploadDate: file.uploadDate, |       uploadDate: file.uploadDate, | ||||||
|       id: file.id, |       id: file.id, | ||||||
|       isOwner: session?.user?.id === file.uploadedById, // Check if the current user is the owner
 |       isOwner: session?.user?.id === file.uploadedById, | ||||||
|       type: file.extension, // Add file type
 |       type: file.extension, | ||||||
|       url: file.url, // Add file URL
 |       url: file.url, | ||||||
|     }); |     }); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     console.error("Error fetching file details:", error); |     console.error("Error fetching file details:", error); | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import { db } from "~/server/db"; | |||||||
| import { auth } from "~/server/auth"; | import { auth } from "~/server/auth"; | ||||||
| import Busboy from "busboy"; | import Busboy from "busboy"; | ||||||
| import { Readable } from "stream"; | import { Readable } from "stream"; | ||||||
| import { notifyClients } from "../files/stream/route"; | import { notifyClients } from "~/utils/notifyClients"; | ||||||
| 
 | 
 | ||||||
| export const config = { | export const config = { | ||||||
|   api: { |   api: { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ import { auth } from "~/server/auth"; | |||||||
| import { HydrateClient } from "~/trpc/server"; | import { HydrateClient } from "~/trpc/server"; | ||||||
| import FileGrid from "~/app/_components/FileGrid"; | import FileGrid from "~/app/_components/FileGrid"; | ||||||
| import UploadForm from "~/app/_components/UploadForm"; | import UploadForm from "~/app/_components/UploadForm"; | ||||||
| import toast, { Toaster } from "react-hot-toast"; | import { Toaster } from "react-hot-toast"; | ||||||
| 
 | 
 | ||||||
| export default async function Home() { | export default async function Home() { | ||||||
|   const session = await auth(); |   const session = await auth(); | ||||||
|  | |||||||
| @ -1,41 +1,44 @@ | |||||||
| "use client"; | "use client"; | ||||||
| 
 | 
 | ||||||
|  | import { Suspense } from "react"; | ||||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| import { useSearchParams, useRouter } from "next/navigation"; | import { useSearchParams, useRouter } from "next/navigation"; | ||||||
| import toast, { Toaster } from "react-hot-toast"; | import toast, { Toaster } from "react-hot-toast"; | ||||||
|  | // import { SharePage } from "~/components/SharePage";
 | ||||||
| 
 | 
 | ||||||
| interface FileDetails { | interface FileDetails { | ||||||
|   name: string; |   name: string; | ||||||
|   size: number; |   size: number; | ||||||
|   owner: string; |   owner: string; | ||||||
|   uploadDate: string; |   uploadDate: string; | ||||||
|   id: string; // Add an ID for the file
 |   id: string; | ||||||
|   isOwner: boolean; // Add a flag to indicate ownership
 |   isOwner: boolean; | ||||||
|   type: string; // Add a type field to differentiate between file types
 |   type: string; | ||||||
|   url: string; // Add a URL field for the file
 |   url: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default function UploadsPage() { | function UploadsPage() { | ||||||
|   const searchParams = useSearchParams(); |   const searchParams = useSearchParams(); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const fileName = searchParams.get("file"); |   const fileId = searchParams.get("id"); | ||||||
|   const [fileDetails, setFileDetails] = useState<FileDetails | null>(null); |   const [fileDetails, setFileDetails] = useState<FileDetails | null>(null); | ||||||
|   const [error, setError] = useState<string | null>(null); |   const [error, setError] = useState<string | null>(null); | ||||||
|  |   // const mediasrc = SharePage() as string; // Replace with a valid string URL or logic to generate the URL
 | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (!fileName) { |     if (!fileId) { | ||||||
|       setError("File name is required."); |       setError("File name is required."); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const fetchFileDetails = async () => { |     const fetchFileDetails = async () => { | ||||||
|       try { |       try { | ||||||
|         const response = await fetch(`/api/share?file=${encodeURIComponent(fileName)}`); |         const response = await fetch(`/api/share?id=${encodeURIComponent(fileId)}`); | ||||||
|         if (!response.ok) { |         if (!response.ok) { | ||||||
|           throw new Error("Failed to fetch file details"); |           throw new Error("Failed to fetch file details"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const data = await response.json(); |         const data: FileDetails = await response.json(); // Explicitly type the response
 | ||||||
|         setFileDetails(data); |         setFileDetails(data); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         console.error(err); |         console.error(err); | ||||||
| @ -43,12 +46,12 @@ export default function UploadsPage() { | |||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     fetchFileDetails(); |     void fetchFileDetails(); // Use `void` to mark the promise as intentionally ignored
 | ||||||
|   }, [fileName]); |   }, [fileId]); | ||||||
| 
 | 
 | ||||||
|   const handleDownload = async () => { |   const handleDownload = async () => { | ||||||
|     try { |     try { | ||||||
|       const response = await fetch(`/api/files/download?fileName=${encodeURIComponent(fileName || "")}`); |       const response = await fetch(`/api/files/download?fileId=${encodeURIComponent(fileId ?? "")}`); | ||||||
|       if (!response.ok) { |       if (!response.ok) { | ||||||
|         throw new Error("Failed to download file"); |         throw new Error("Failed to download file"); | ||||||
|       } |       } | ||||||
| @ -57,7 +60,7 @@ export default function UploadsPage() { | |||||||
|       const url = window.URL.createObjectURL(blob); |       const url = window.URL.createObjectURL(blob); | ||||||
|       const a = document.createElement("a"); |       const a = document.createElement("a"); | ||||||
|       a.href = url; |       a.href = url; | ||||||
|       a.download = fileName || "downloaded-file"; |       a.download = fileId ?? "downloaded-file"; // Use nullish coalescing
 | ||||||
|       document.body.appendChild(a); |       document.body.appendChild(a); | ||||||
|       a.click(); |       a.click(); | ||||||
|       a.remove(); |       a.remove(); | ||||||
| @ -87,7 +90,7 @@ export default function UploadsPage() { | |||||||
|         headers: { |         headers: { | ||||||
|           "Content-Type": "application/json", |           "Content-Type": "application/json", | ||||||
|         }, |         }, | ||||||
|         body: JSON.stringify({ id: fileDetails?.id }), // Use the ID of the file
 |         body: JSON.stringify({ id: fileDetails?.id }), // Use optional chaining
 | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       if (!response.ok) { |       if (!response.ok) { | ||||||
| @ -108,23 +111,23 @@ export default function UploadsPage() { | |||||||
| 
 | 
 | ||||||
|   if (!fileDetails) { |   if (!fileDetails) { | ||||||
|     return ( |     return ( | ||||||
|         <main className="relative flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white"> |       <main className="relative flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white"> | ||||||
|       <Toaster position="top-right" reverseOrder={false} /> |         <Toaster position="top-right" reverseOrder={false} /> | ||||||
|       <div className="absolute top-4 left-4"> |         <div className="absolute top-4 left-4"> | ||||||
|         <button |           <button | ||||||
|           onClick={() => router.push("/")} |             onClick={() => router.push("/")} | ||||||
|           className="rounded-full bg-white/10 px-4 py-2 font-semibold no-underline transition hover:bg-white/20" |             className="rounded-full bg-white/10 px-4 py-2 font-semibold no-underline transition hover:bg-white/20" | ||||||
|         > |           > | ||||||
|           Home |             Home | ||||||
|         </button> |           </button> | ||||||
|       </div> |         </div> | ||||||
|       <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16"> |         <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16"> | ||||||
|         <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]"> |           <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]"> | ||||||
|           <span className="text-[hsl(280,100%,70%)]">File</span> Details |             <span className="text-[hsl(280,100%,70%)]">File</span> Details | ||||||
|         </h1> |           </h1> | ||||||
|       </div> |         </div> | ||||||
|     </main> |       </main> | ||||||
|   ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
| @ -142,17 +145,21 @@ export default function UploadsPage() { | |||||||
|         <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]"> |         <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]"> | ||||||
|           <span className="text-[hsl(280,100%,70%)]">File</span> Details |           <span className="text-[hsl(280,100%,70%)]">File</span> Details | ||||||
|         </h1> |         </h1> | ||||||
|         <div className=" mt-6"> |         <div className="mt-6"> | ||||||
|             {(fileDetails.type.startsWith(".png") || fileDetails.type.startsWith(".jpg") || fileDetails.type.startsWith(".jpeg") || fileDetails.type.startsWith(".gif")) && ( |           {/* {(fileDetails.type.startsWith(".png") || | ||||||
|               <SharePage /> |             fileDetails.type.startsWith(".jpg") || | ||||||
|             )} |             fileDetails.type.startsWith(".jpeg") || | ||||||
|             {(fileDetails.type.startsWith(".mp4") || fileDetails.type.startsWith(".webm") || fileDetails.type.startsWith(".ogg")) && ( |             fileDetails.type.startsWith(".gif")) && (mediasrc && <img src={mediasrc} alt="Media preview" />)} | ||||||
|  |           {(fileDetails.type.startsWith(".mp4") || | ||||||
|  |             fileDetails.type.startsWith(".webm") || | ||||||
|  |             fileDetails.type.startsWith(".ogg")) && | ||||||
|  |               (mediasrc && | ||||||
|               <video controls className="max-w-full max-h-96 rounded-lg shadow-md"> |               <video controls className="max-w-full max-h-96 rounded-lg shadow-md"> | ||||||
|               <SharePage /> |                 <source src={mediasrc} type="video" /> | ||||||
|               Your browser does not support the video tag. |                 Your browser does not support the video tag. | ||||||
|               </video> |               </video> | ||||||
|             )} |               )} */} | ||||||
|           </div> |         </div> | ||||||
|         <div className="bg-white/10 shadow-md rounded-lg p-6 w-full max-w-md text-white"> |         <div className="bg-white/10 shadow-md rounded-lg p-6 w-full max-w-md text-white"> | ||||||
|           <p> |           <p> | ||||||
|             <strong>Name:</strong> {fileDetails.name} |             <strong>Name:</strong> {fileDetails.name} | ||||||
| @ -167,7 +174,6 @@ export default function UploadsPage() { | |||||||
|             <strong>Upload Date:</strong> {new Date(fileDetails.uploadDate).toLocaleString()} |             <strong>Upload Date:</strong> {new Date(fileDetails.uploadDate).toLocaleString()} | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|         {/* Preview Section */} |  | ||||||
|         <div className="flex gap-4 mt-6"> |         <div className="flex gap-4 mt-6"> | ||||||
|           <button |           <button | ||||||
|             onClick={handleDownload} |             onClick={handleDownload} | ||||||
| @ -197,52 +203,10 @@ export default function UploadsPage() { | |||||||
|   ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function SharePage() { | export default function Page() { | ||||||
|   const searchParams = useSearchParams(); |  | ||||||
|   const fileName = searchParams.get("file"); |  | ||||||
|   const [imageSrc, setImageSrc] = useState<string | null>(null); |  | ||||||
|   const [error, setError] = useState<string | null>(null); |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
| 
 |  | ||||||
|     const fetchImage = async () => { |  | ||||||
|       try { |  | ||||||
|         if (!fileName) { |  | ||||||
|           throw new Error("File name is required."); |  | ||||||
|         } |  | ||||||
|         const response = await fetch(`/api/serv?id=${encodeURIComponent(fileName)}`); |  | ||||||
|         if (!response.ok) { |  | ||||||
|           throw new Error("Failed to fetch image"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const blob = await response.blob(); |  | ||||||
|         const objectUrl = URL.createObjectURL(blob); |  | ||||||
|         setImageSrc(objectUrl); |  | ||||||
|       } catch (err) { |  | ||||||
|         console.error(err); |  | ||||||
|         setError("Failed to load image."); |  | ||||||
|         console.log(err); |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     fetchImage(); |  | ||||||
| 
 |  | ||||||
|     return () => { |  | ||||||
|       if (imageSrc) { |  | ||||||
|         URL.revokeObjectURL(imageSrc); // Clean up the object URL
 |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|   }, [fileName]); |  | ||||||
| 
 |  | ||||||
|   if (error) { |  | ||||||
|     return <div className="text-red-500">{error}</div>; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (!imageSrc) { |  | ||||||
|     return <div>Loading...</div>; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return ( |   return ( | ||||||
|     <source src={imageSrc} className="max-w-full max-h-96 rounded-lg shadow-md" /> |     <Suspense fallback={<div>Loading...</div>}> | ||||||
|  |       <UploadsPage /> | ||||||
|  |     </Suspense> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
							
								
								
									
										52
									
								
								src/components/SharePage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/components/SharePage.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | "use client"; | ||||||
|  | 
 | ||||||
|  | import { useEffect, useState } from "react"; | ||||||
|  | import { useSearchParams } from "next/navigation"; | ||||||
|  | 
 | ||||||
|  | export function SharePage() { | ||||||
|  |   const searchParams = useSearchParams(); | ||||||
|  |   const fileId = searchParams.get("id"); | ||||||
|  |   const [imageSrc, setImageSrc] = useState<string | null>(null); | ||||||
|  |   const [error, setError] = useState<string | null>(null); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     const fetchImage = async () => { | ||||||
|  |       try { | ||||||
|  |         if (!fileId) { | ||||||
|  |           throw new Error("File name is required."); | ||||||
|  |         } | ||||||
|  |         const response = await fetch(`/api/serv?id=${encodeURIComponent(fileId)}`); | ||||||
|  |         if (!response.ok) { | ||||||
|  |           throw new Error("Failed to fetch image"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const blob = await response.blob(); | ||||||
|  |         const objectUrl = URL.createObjectURL(blob); | ||||||
|  |         setImageSrc(objectUrl); | ||||||
|  |       } catch (err) { | ||||||
|  |         console.error(err); | ||||||
|  |         setError("Failed to load image."); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     void fetchImage(); | ||||||
|  | 
 | ||||||
|  |     return () => { | ||||||
|  |       if (imageSrc) { | ||||||
|  |         URL.revokeObjectURL(imageSrc); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   }, [fileId, imageSrc]); | ||||||
|  | 
 | ||||||
|  |   if (error) { | ||||||
|  |     return <div className="text-red-500">{error}</div>; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!imageSrc) { | ||||||
|  |     return <div>Loading...</div>; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |       imageSrc | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @ -1,6 +1,8 @@ | |||||||
| import { createEnv } from "@t3-oss/env-nextjs"; | import { createEnv } from "@t3-oss/env-nextjs"; | ||||||
| import { z } from "zod"; | import { z } from "zod"; | ||||||
| 
 | 
 | ||||||
|  | // console.log("Environment Variables:", process.env);
 | ||||||
|  | 
 | ||||||
| export const env = createEnv({ | export const env = createEnv({ | ||||||
|   /** |   /** | ||||||
|    * Specify your server-side environment variables schema here. This way you can ensure the app |    * Specify your server-side environment variables schema here. This way you can ensure the app | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								src/utils/notifyClients.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/utils/notifyClients.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | interface Client { | ||||||
|  |   send: (data: string) => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const clients: Set<Client> = new Set<Client>(); | ||||||
|  | 
 | ||||||
|  | export function notifyClients(data: unknown) { | ||||||
|  |   const message = JSON.stringify(data); | ||||||
|  |   clients.forEach((client) => { | ||||||
|  |     try { | ||||||
|  |       client.send(message); | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error("Failed to send message to a client:", error); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { clients }; | ||||||
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user