Skip to content

Commit 0ff8df0

Browse files
fix(files): resolve file id route params (#122)
1 parent 2305892 commit 0ff8df0

2 files changed

Lines changed: 92 additions & 12 deletions

File tree

‎src/app/api/files/[fileId]/route.js‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { NextResponse } from 'next/server';
33
import { createSupabaseServerClient } from '@/lib/supabase.js';
44
import { postQuantumEncryption } from '@/lib/crypto/post-quantum-encryption.js';
55

6+
async function resolveRouteParams(params) {
7+
return (await params) || {};
8+
}
9+
610
export async function GET(request, { params } = {}) {
711
try {
812
// Create Supabase server client
@@ -14,7 +18,7 @@ export async function GET(request, { params } = {}) {
1418
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
1519
}
1620

17-
const fileId = params.fileId;
21+
const { fileId } = await resolveRouteParams(params);
1822
console.log(`📁 [FILE-DOWNLOAD] Download request from auth user: ${user.id} for file: ${fileId}`);
1923

2024
// Get the internal user ID from the users table using auth_user_id
@@ -117,7 +121,7 @@ export async function GET(request, { params } = {}) {
117121
const metadataObj = JSON.parse(decryptedMetadata);
118122
mimeType = metadataObj.mimeType || 'application/octet-stream';
119123
}
120-
} catch (metaError) {
124+
} catch {
121125
console.warn('📁 [FILE-DOWNLOAD] Could not decrypt metadata for mimeType');
122126
}
123127

@@ -146,7 +150,7 @@ export async function GET(request, { params } = {}) {
146150
}
147151

148152
// Get file info without downloading the actual file content
149-
export async function HEAD(event) {
153+
export async function HEAD(request, { params } = {}) {
150154
try {
151155
// Create Supabase server client
152156
const supabase = await createSupabaseServerClient();
@@ -158,7 +162,7 @@ export async function HEAD(event) {
158162
}
159163

160164
const userId = user.id;
161-
const fileId = params.fileId;
165+
const { fileId } = await resolveRouteParams(params);
162166

163167
// Get file metadata from database
164168
const { data: fileData, error: fileError } = await supabase
@@ -212,7 +216,7 @@ export async function POST(request, { params } = {}) {
212216
}
213217

214218
const userId = user.id;
215-
const fileId = params.fileId;
219+
const { fileId } = await resolveRouteParams(params);
216220

217221
console.log(`📁 [FILE-INFO] Info request from user: ${userId} for file: ${fileId}`);
218222

@@ -259,10 +263,3 @@ export async function POST(request, { params } = {}) {
259263
}
260264
}
261265

262-
function formatFileSize(bytes) {
263-
if (bytes === 0) return '0 Bytes';
264-
const k = 1024;
265-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
266-
const i = Math.floor(Math.log(bytes) / Math.log(k));
267-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
268-
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const mocks = vi.hoisted(() => ({
4+
authGetUser: vi.fn(),
5+
from: vi.fn(),
6+
eq: vi.fn()
7+
}));
8+
9+
vi.mock('@/lib/supabase.js', () => ({
10+
createSupabaseServerClient: vi.fn(async () => ({
11+
auth: {
12+
getUser: mocks.authGetUser
13+
},
14+
from: mocks.from
15+
}))
16+
}));
17+
18+
vi.mock('@/lib/crypto/post-quantum-encryption.js', () => ({
19+
postQuantumEncryption: {
20+
initialize: vi.fn(),
21+
decryptFromSender: vi.fn()
22+
}
23+
}));
24+
25+
function createFileQuery() {
26+
const query = {
27+
select: vi.fn(() => query),
28+
eq: mocks.eq,
29+
single: vi.fn().mockResolvedValue({
30+
data: {
31+
id: 'file-1',
32+
message_id: 'message-1',
33+
encrypted_metadata: {},
34+
created_at: '2026-01-01T00:00:00.000Z',
35+
created_by: 'user-1',
36+
messages: [{ conversation_id: 'conversation-1' }]
37+
},
38+
error: null
39+
})
40+
};
41+
mocks.eq.mockReturnValue(query);
42+
return query;
43+
}
44+
45+
describe('/api/files/[fileId]', () => {
46+
beforeEach(() => {
47+
vi.resetModules();
48+
vi.clearAllMocks();
49+
50+
mocks.authGetUser.mockResolvedValue({
51+
data: { user: { id: 'user-1' } },
52+
error: null
53+
});
54+
mocks.from.mockImplementation((table) => {
55+
if (table === 'encrypted_files') return createFileQuery();
56+
throw new Error(`Unexpected table: ${table}`);
57+
});
58+
});
59+
60+
it('resolves async route params for HEAD metadata requests', async () => {
61+
const { HEAD } = await import('./route.js');
62+
const response = await HEAD(new Request('https://qrypt.chat/api/files/file-1'), {
63+
params: Promise.resolve({ fileId: 'file-1' })
64+
});
65+
66+
expect(response.status).toBe(200);
67+
expect(response.headers.get('Content-Type')).toBe('application/octet-stream');
68+
expect(mocks.eq).toHaveBeenCalledWith('id', 'file-1');
69+
});
70+
71+
it('resolves async route params for POST metadata requests', async () => {
72+
const { POST } = await import('./route.js');
73+
const response = await POST(new Request('https://qrypt.chat/api/files/file-1', { method: 'POST' }), {
74+
params: Promise.resolve({ fileId: 'file-1' })
75+
});
76+
const body = await response.json();
77+
78+
expect(response.status).toBe(200);
79+
expect(body.id).toBe('file-1');
80+
expect(body.conversationId).toBe('conversation-1');
81+
expect(mocks.eq).toHaveBeenCalledWith('id', 'file-1');
82+
});
83+
});

0 commit comments

Comments
 (0)