From fde0dd72169bf1a805cf6cce93adf69526bb9976 Mon Sep 17 00:00:00 2001 From: b3ni15 Date: Thu, 16 Oct 2025 11:25:08 +0200 Subject: [PATCH] refactor: Improve type safety in download and file routes by updating type annotations --- .../[file_id]/[part_index]/route.ts | 15 ++++++---- src/app/api/file/[file_id]/route.ts | 29 ++++++++++++++----- src/app/download/[file_id]/page.tsx | 9 ++++-- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/app/api/download-part/[file_id]/[part_index]/route.ts b/src/app/api/download-part/[file_id]/[part_index]/route.ts index 25ed85d..dd49173 100644 --- a/src/app/api/download-part/[file_id]/[part_index]/route.ts +++ b/src/app/api/download-part/[file_id]/[part_index]/route.ts @@ -1,4 +1,4 @@ -import { NextResponse } from 'next/server'; +import { NextResponse, NextRequest } from 'next/server'; import pool from '@/lib/db'; import { RowDataPacket } from 'mysql2/promise'; @@ -6,15 +6,20 @@ interface FilePart extends RowDataPacket { discord_message_id: string | null; } +interface RouteContext { + params: { + file_id: string; + part_index: string; + }; +} + if (!process.env.DISCORD_BOT_TOKEN || !process.env.DISCORD_CHANNEL_ID) { throw new Error('Discord bot token or channel ID is not configured'); } -export async function GET(request: Request, { params }: { params: { file_id: string; part_index: string; } }) { +export async function GET(request: NextRequest, context: { params: Record }) { try { - const params = await context.params; - const file_id = params.file_id as string; - const part_index = params.part_index as string; + const { file_id, part_index } = params; // NOTE: In a real-world scenario, you MUST validate if the user has permission to download this part. // This would involve checking the download_token against the hash in the `files` table. diff --git a/src/app/api/file/[file_id]/route.ts b/src/app/api/file/[file_id]/route.ts index b99337d..1183740 100644 --- a/src/app/api/file/[file_id]/route.ts +++ b/src/app/api/file/[file_id]/route.ts @@ -1,11 +1,24 @@ -import { NextResponse } from 'next/server'; +import { NextResponse, NextRequest } from 'next/server'; import pool from '@/lib/db'; import bcrypt from 'bcrypt'; +import { RowDataPacket } from 'mysql2/promise'; -export async function GET(request: Request, context: any) { +interface FileData extends RowDataPacket { + filename: string; + size: number; + num_parts: number; + expires_at: Date | null; + token_hash: string; +} + +interface FilePartMetadata extends RowDataPacket { + part_index: number; + size: number; +} + +export async function GET(request: NextRequest, { params }: { params: { file_id: string; } }) { try { - const params = await context.params; - const file_id = params.file_id as string; + const file_id = params.file_id; const { searchParams } = new URL(request.url); const token = searchParams.get('token'); @@ -16,8 +29,8 @@ export async function GET(request: Request, context: any) { const connection = await pool.getConnection(); try { // 1. Fetch file metadata and token hash - const [fileRows]: any[] = await connection.query('SELECT * FROM files WHERE id = ? AND deleted = 0', [file_id]); - const file = fileRows[0]; + const [fileRows]: [RowDataPacket[], unknown] = await connection.query('SELECT * FROM files WHERE id = ? AND deleted = 0', [file_id]); + const file = fileRows[0] as FileData; if (!file) { return NextResponse.json({ error: 'File not found' }, { status: 404 }); @@ -35,7 +48,7 @@ export async function GET(request: Request, context: any) { } // 4. Fetch file parts - const [partsRows]: any[] = await connection.query( + const [partsRows]: [RowDataPacket[], unknown] = await connection.query( 'SELECT part_index, size FROM file_parts WHERE file_id = ? ORDER BY part_index ASC', [file_id] ); @@ -45,7 +58,7 @@ export async function GET(request: Request, context: any) { filename: file.filename, size: file.size, num_parts: file.num_parts, - parts: partsRows, + parts: partsRows[0] as FilePartMetadata[], }; return NextResponse.json(response); diff --git a/src/app/download/[file_id]/page.tsx b/src/app/download/[file_id]/page.tsx index 19243e6..26f0a29 100644 --- a/src/app/download/[file_id]/page.tsx +++ b/src/app/download/[file_id]/page.tsx @@ -3,6 +3,11 @@ import { useEffect, useState, useRef } from 'react'; import { useParams, useSearchParams } from 'next/navigation'; +interface MetadataPart { + part_index: number; + size: number; +} + // Helper to convert Base64 string back to ArrayBuffer function base64ToBuffer(base64: string) { const binary_string = window.atob(base64.replace(/-/g, '+').replace(/_/g, '/')); @@ -106,7 +111,7 @@ export default function DownloadPage() { setDownloadState('downloading'); const encryptedParts = new Array(metadata.num_parts); - const downloadPromises = metadata.parts.map(async (part: any) => { + const downloadPromises = metadata.parts.map(async (part: MetadataPart) => { const response = await fetchWithRetry(`/api/download-part/${file_id}/${part.part_index}`); const buffer = await response.arrayBuffer(); encryptedParts[part.part_index] = { index: part.part_index, data: buffer }; @@ -127,7 +132,7 @@ export default function DownloadPage() { ['decrypt'] ); - const decryptPromises = encryptedParts.map(async (part) => { + const decryptPromises = encryptedParts.map(async (part: { index: number; data: ArrayBuffer }) => { const iv = part.data.slice(0, 12); const data = part.data.slice(12); const decrypted = await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv }, fileKey, data);