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 = { | ||||
|   reactStrictMode: true, | ||||
|   experimental: {}, | ||||
|   eslint: { | ||||
|     ignoreDuringBuilds: true, | ||||
|   }, | ||||
|   images: { | ||||
|     remotePatterns: [ | ||||
|       { | ||||
|         protocol: "https", | ||||
|         hostname: "cdn.discordapp.com", | ||||
|         port: "", | ||||
|         pathname: "/**", | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export default nextConfig; | ||||
|  | ||||
| @ -1,3 +1,6 @@ | ||||
| //!eslint-disable @typescript-eslint/no-unsafe-assignment
 | ||||
| //!eslint-disable @typescript-eslint/no-unsafe-argument
 | ||||
| 
 | ||||
| "use client"; | ||||
| 
 | ||||
| import { useEffect, useState } from "react"; | ||||
| @ -17,8 +20,7 @@ interface FileGridProps { | ||||
| export default function FileGrid({ session }: FileGridProps) { | ||||
|   const [files, setFiles] = useState<File[]>([]); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
| 
 | ||||
|    | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
|   const fetchFiles = async () => { | ||||
|     try { | ||||
| @ -26,7 +28,7 @@ export default function FileGrid({ session }: FileGridProps) { | ||||
|       if (!response.ok) { | ||||
|         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); | ||||
|     } catch (err) { | ||||
|       console.error(err); | ||||
| @ -71,12 +73,14 @@ export default function FileGrid({ session }: FileGridProps) { | ||||
|     const eventSource = new EventSource("/api/files/stream"); | ||||
| 
 | ||||
|     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") { | ||||
|         setFiles((prevFiles) => [...prevFiles, data.file]); | ||||
|       if (data.type === "file-added" && data.file) { | ||||
|         if (data.file) { | ||||
|           setFiles((prevFiles) => [...prevFiles, data.file as File]); | ||||
|         } | ||||
|         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)); | ||||
|       } | ||||
|     }; | ||||
| @ -123,7 +127,6 @@ export default function FileGrid({ session }: FileGridProps) { | ||||
|   if (error) { | ||||
|     return <div className="text-red-500">{error}</div>; | ||||
|   } | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
|   return ( | ||||
|       <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 = () => { | ||||
|         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
 | ||||
|           toast.success("File uploaded successfully!"); | ||||
| 
 | ||||
|  | ||||
| @ -4,20 +4,20 @@ import { promises as fs } from "fs"; | ||||
| 
 | ||||
| export async function GET(req: Request) { | ||||
|   const url = new URL(req.url); | ||||
|   const fileName = url.searchParams.get("fileName"); | ||||
|   const fileId = url.searchParams.get("fileName"); | ||||
| 
 | ||||
|   if (!fileName) { | ||||
|     return NextResponse.json({ error: "File name is required" }, { status: 400 }); | ||||
|   if (!fileId) { | ||||
|     return NextResponse.json({ error: "File id is required" }, { status: 400 }); | ||||
|   } | ||||
| 
 | ||||
|   try { | ||||
|     const filePath = path.join(process.cwd(), "uploads", fileName); | ||||
|     const filePath = path.join(process.cwd(), "uploads", fileId); | ||||
|     const fileBuffer = await fs.readFile(filePath); | ||||
| 
 | ||||
|     return new Response(fileBuffer, { | ||||
|       headers: { | ||||
|         "Content-Type": "application/octet-stream", | ||||
|         "Content-Disposition": `attachment; filename="${fileName}"`, | ||||
|         "Content-Disposition": `attachment; filename="${fileId}"`, | ||||
|       }, | ||||
|     }); | ||||
|   } catch (error) { | ||||
|  | ||||
| @ -1,6 +1,4 @@ | ||||
| import { NextResponse } from "next/server"; | ||||
| 
 | ||||
| const clients: Set<any> = new Set(); | ||||
| import { clients } from "~/utils/notifyClients"; | ||||
| 
 | ||||
| export async function GET() { | ||||
|   const stream = new ReadableStream({ | ||||
| @ -20,16 +18,9 @@ export async function GET() { | ||||
| 
 | ||||
|       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.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 path from "path"; | ||||
| import { promises as fs } from "fs"; | ||||
| import { notifyClients } from "../files/stream/route"; | ||||
| import { notifyClients } from "~/utils/notifyClients"; | ||||
| 
 | ||||
| export async function DELETE(req: Request) { | ||||
|   const session = await auth(); | ||||
| @ -13,15 +13,13 @@ export async function DELETE(req: Request) { | ||||
|   } | ||||
| 
 | ||||
|   try { | ||||
|     const body = await req.json().catch(() => null); // Handle empty or invalid JSON
 | ||||
|     if (!body || !body.id) { | ||||
|     const body = (await req.json()) as { id: string } | null; | ||||
|     if (!body?.id) { | ||||
|       return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); | ||||
|     } | ||||
| 
 | ||||
|     const resourceId = body.id; | ||||
| 
 | ||||
|     const resource = await db.file.findUnique({ | ||||
|       where: { id: resourceId }, | ||||
|       where: { id: body.id }, | ||||
|     }); | ||||
| 
 | ||||
|     if (!resource || resource.uploadedById !== session.user.id) { | ||||
| @ -34,11 +32,10 @@ export async function DELETE(req: Request) { | ||||
|     }); | ||||
| 
 | ||||
|     await db.file.delete({ | ||||
|       where: { id: resourceId }, | ||||
|       where: { id: body.id }, | ||||
|     }); | ||||
| 
 | ||||
|     // Notify clients about the deleted file
 | ||||
|     notifyClients({ type: "file-removed", fileId: resourceId }); | ||||
|     notifyClients({ type: "file-removed", fileId: body.id }); | ||||
| 
 | ||||
|     return NextResponse.json({ message: "Resource deleted successfully" }); | ||||
|   } catch (error) { | ||||
|  | ||||
| @ -2,12 +2,10 @@ import { NextResponse } from "next/server"; | ||||
| import { db } from "~/server/db"; | ||||
| import { auth } from "~/server/auth"; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| export async function GET(req: Request) { | ||||
|   const session = await auth(); | ||||
|   const url = new URL(req.url); | ||||
|   const fileName = url.searchParams.get("file"); | ||||
|   const fileName = url.searchParams.get("id"); | ||||
| 
 | ||||
|   if (!fileName) { | ||||
|     return NextResponse.json({ error: "File name is required" }, { status: 400 }); | ||||
| @ -26,12 +24,12 @@ export async function GET(req: Request) { | ||||
|     return NextResponse.json({ | ||||
|       name: file.name, | ||||
|       size: file.size, | ||||
|       owner: file.uploadedBy?.name || "Unknown", | ||||
|       owner: file.uploadedBy?.name ?? "Unknown", // Use nullish coalescing
 | ||||
|       uploadDate: file.uploadDate, | ||||
|       id: file.id, | ||||
|       isOwner: session?.user?.id === file.uploadedById, // Check if the current user is the owner
 | ||||
|       type: file.extension, // Add file type
 | ||||
|       url: file.url, // Add file URL
 | ||||
|       isOwner: session?.user?.id === file.uploadedById, | ||||
|       type: file.extension, | ||||
|       url: file.url, | ||||
|     }); | ||||
|   } catch (error) { | ||||
|     console.error("Error fetching file details:", error); | ||||
|  | ||||
| @ -5,7 +5,7 @@ import { db } from "~/server/db"; | ||||
| import { auth } from "~/server/auth"; | ||||
| import Busboy from "busboy"; | ||||
| import { Readable } from "stream"; | ||||
| import { notifyClients } from "../files/stream/route"; | ||||
| import { notifyClients } from "~/utils/notifyClients"; | ||||
| 
 | ||||
| export const config = { | ||||
|   api: { | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { auth } from "~/server/auth"; | ||||
| import { HydrateClient } from "~/trpc/server"; | ||||
| import FileGrid from "~/app/_components/FileGrid"; | ||||
| import UploadForm from "~/app/_components/UploadForm"; | ||||
| import toast, { Toaster } from "react-hot-toast"; | ||||
| import { Toaster } from "react-hot-toast"; | ||||
| 
 | ||||
| export default async function Home() { | ||||
|   const session = await auth(); | ||||
|  | ||||
| @ -1,41 +1,44 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { Suspense } from "react"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import { useSearchParams, useRouter } from "next/navigation"; | ||||
| import toast, { Toaster } from "react-hot-toast"; | ||||
| // import { SharePage } from "~/components/SharePage";
 | ||||
| 
 | ||||
| interface FileDetails { | ||||
|   name: string; | ||||
|   size: number; | ||||
|   owner: string; | ||||
|   uploadDate: string; | ||||
|   id: string; // Add an ID for the file
 | ||||
|   isOwner: boolean; // Add a flag to indicate ownership
 | ||||
|   type: string; // Add a type field to differentiate between file types
 | ||||
|   url: string; // Add a URL field for the file
 | ||||
|   id: string; | ||||
|   isOwner: boolean; | ||||
|   type: string; | ||||
|   url: string; | ||||
| } | ||||
| 
 | ||||
| export default function UploadsPage() { | ||||
| function UploadsPage() { | ||||
|   const searchParams = useSearchParams(); | ||||
|   const router = useRouter(); | ||||
|   const fileName = searchParams.get("file"); | ||||
|   const fileId = searchParams.get("id"); | ||||
|   const [fileDetails, setFileDetails] = useState<FileDetails | 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(() => { | ||||
|     if (!fileName) { | ||||
|     if (!fileId) { | ||||
|       setError("File name is required."); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const fetchFileDetails = async () => { | ||||
|       try { | ||||
|         const response = await fetch(`/api/share?file=${encodeURIComponent(fileName)}`); | ||||
|         const response = await fetch(`/api/share?id=${encodeURIComponent(fileId)}`); | ||||
|         if (!response.ok) { | ||||
|           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); | ||||
|       } catch (err) { | ||||
|         console.error(err); | ||||
| @ -43,12 +46,12 @@ export default function UploadsPage() { | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     fetchFileDetails(); | ||||
|   }, [fileName]); | ||||
|     void fetchFileDetails(); // Use `void` to mark the promise as intentionally ignored
 | ||||
|   }, [fileId]); | ||||
| 
 | ||||
|   const handleDownload = async () => { | ||||
|     try { | ||||
|       const response = await fetch(`/api/files/download?fileName=${encodeURIComponent(fileName || "")}`); | ||||
|       const response = await fetch(`/api/files/download?fileId=${encodeURIComponent(fileId ?? "")}`); | ||||
|       if (!response.ok) { | ||||
|         throw new Error("Failed to download file"); | ||||
|       } | ||||
| @ -57,7 +60,7 @@ export default function UploadsPage() { | ||||
|       const url = window.URL.createObjectURL(blob); | ||||
|       const a = document.createElement("a"); | ||||
|       a.href = url; | ||||
|       a.download = fileName || "downloaded-file"; | ||||
|       a.download = fileId ?? "downloaded-file"; // Use nullish coalescing
 | ||||
|       document.body.appendChild(a); | ||||
|       a.click(); | ||||
|       a.remove(); | ||||
| @ -87,7 +90,7 @@ export default function UploadsPage() { | ||||
|         headers: { | ||||
|           "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) { | ||||
| @ -143,15 +146,19 @@ export default function UploadsPage() { | ||||
|           <span className="text-[hsl(280,100%,70%)]">File</span> Details | ||||
|         </h1> | ||||
|         <div className="mt-6"> | ||||
|             {(fileDetails.type.startsWith(".png") || fileDetails.type.startsWith(".jpg") || fileDetails.type.startsWith(".jpeg") || fileDetails.type.startsWith(".gif")) && ( | ||||
|               <SharePage /> | ||||
|             )} | ||||
|             {(fileDetails.type.startsWith(".mp4") || fileDetails.type.startsWith(".webm") || fileDetails.type.startsWith(".ogg")) && ( | ||||
|           {/* {(fileDetails.type.startsWith(".png") || | ||||
|             fileDetails.type.startsWith(".jpg") || | ||||
|             fileDetails.type.startsWith(".jpeg") || | ||||
|             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"> | ||||
|               <SharePage /> | ||||
|                 <source src={mediasrc} type="video" /> | ||||
|                 Your browser does not support the video tag. | ||||
|               </video> | ||||
|             )} | ||||
|               )} */} | ||||
|         </div> | ||||
|         <div className="bg-white/10 shadow-md rounded-lg p-6 w-full max-w-md text-white"> | ||||
|           <p> | ||||
| @ -167,7 +174,6 @@ export default function UploadsPage() { | ||||
|             <strong>Upload Date:</strong> {new Date(fileDetails.uploadDate).toLocaleString()} | ||||
|           </p> | ||||
|         </div> | ||||
|         {/* Preview Section */} | ||||
|         <div className="flex gap-4 mt-6"> | ||||
|           <button | ||||
|             onClick={handleDownload} | ||||
| @ -197,52 +203,10 @@ export default function UploadsPage() { | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export function SharePage() { | ||||
|   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>; | ||||
|   } | ||||
| 
 | ||||
| export default function Page() { | ||||
|   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 { z } from "zod"; | ||||
| 
 | ||||
| // console.log("Environment Variables:", process.env);
 | ||||
| 
 | ||||
| export const env = createEnv({ | ||||
|   /** | ||||
|    * 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