refactor: Improve type safety in download and file routes by updating type annotations

This commit is contained in:
2025-10-16 11:25:08 +02:00
parent cf72ed90e7
commit fde0dd7216
3 changed files with 38 additions and 15 deletions

View File

@@ -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<string, string | string[]> }) {
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.

View File

@@ -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);

View File

@@ -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);