184 lines
5.3 KiB
TypeScript
184 lines
5.3 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { getServerSession } from 'next-auth';
|
|
import { authOptions } from "@/app/api/auth/options";
|
|
import { getObjectContent } from '@/lib/s3';
|
|
|
|
// Error types for better error handling
|
|
enum StorageError {
|
|
UNAUTHORIZED = 'UNAUTHORIZED',
|
|
FORBIDDEN = 'FORBIDDEN',
|
|
NOT_FOUND = 'NOT_FOUND',
|
|
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
|
S3_ERROR = 'S3_ERROR',
|
|
INTERNAL_ERROR = 'INTERNAL_ERROR'
|
|
}
|
|
|
|
// Helper function to create error response
|
|
function createErrorResponse(
|
|
error: StorageError,
|
|
message: string,
|
|
status: number,
|
|
details?: any
|
|
) {
|
|
return NextResponse.json(
|
|
{
|
|
error: error,
|
|
message: message,
|
|
...(details && { details })
|
|
},
|
|
{ status }
|
|
);
|
|
}
|
|
|
|
// Helper function to check authentication
|
|
async function checkAuth(request: Request) {
|
|
const session = await getServerSession(authOptions);
|
|
if (!session?.user?.id) {
|
|
console.error('Unauthorized access attempt:', {
|
|
url: request.url,
|
|
method: request.method,
|
|
headers: Object.fromEntries(request.headers)
|
|
});
|
|
return { authorized: false, userId: null };
|
|
}
|
|
return { authorized: true, userId: session.user.id };
|
|
}
|
|
|
|
export async function GET(request: Request) {
|
|
try {
|
|
const { authorized, userId } = await checkAuth(request);
|
|
if (!authorized || !userId) {
|
|
return createErrorResponse(
|
|
StorageError.UNAUTHORIZED,
|
|
'Authentication required',
|
|
401
|
|
);
|
|
}
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const path = searchParams.get('path');
|
|
const id = searchParams.get('id');
|
|
|
|
if (!path && !id) {
|
|
return createErrorResponse(
|
|
StorageError.VALIDATION_ERROR,
|
|
'Path or ID parameter is required',
|
|
400,
|
|
{ missing: 'path or id' }
|
|
);
|
|
}
|
|
|
|
// Determine the key to use
|
|
let key: string;
|
|
|
|
if (id) {
|
|
// If id is provided directly, use it as the key
|
|
key = id;
|
|
|
|
// Ensure the user can only access their own files
|
|
if (!key.startsWith(`user-${userId}/`)) {
|
|
console.error('Unauthorized file access attempt:', { userId, fileId: id });
|
|
return createErrorResponse(
|
|
StorageError.FORBIDDEN,
|
|
'Unauthorized access to file',
|
|
403,
|
|
{ fileId: id }
|
|
);
|
|
}
|
|
} else if (path) {
|
|
// If a path is provided, ensure it contains the user's ID
|
|
if (!path.includes(`/files/cube-${userId}/`) && !path.includes(`user-${userId}/`)) {
|
|
// For backward compatibility, convert NextCloud path to S3 path
|
|
if (path.startsWith('/files/') || path.includes('/Private/')) {
|
|
// Extract folder and filename from path
|
|
const parts = path.split('/').filter(Boolean);
|
|
const file = parts[parts.length - 1];
|
|
let folder = 'notes'; // Default folder
|
|
|
|
// Try to determine folder from path
|
|
if (path.includes('/Notes/')) folder = 'notes';
|
|
else if (path.includes('/Diary/')) folder = 'diary';
|
|
else if (path.includes('/Contacts/')) folder = 'contacts';
|
|
else if (path.includes('/Health/')) folder = 'health';
|
|
|
|
// Use direct user path without pages prefix
|
|
key = `user-${userId}/${folder}/${file}`;
|
|
console.log('Converted NextCloud path to S3 key:', { path, key });
|
|
} else {
|
|
console.error('Unauthorized file access attempt:', { userId, filePath: path });
|
|
return createErrorResponse(
|
|
StorageError.FORBIDDEN,
|
|
'Unauthorized access to file',
|
|
403,
|
|
{ filePath: path }
|
|
);
|
|
}
|
|
} else {
|
|
// If it already contains user ID, use the path directly
|
|
key = path;
|
|
}
|
|
} else {
|
|
return createErrorResponse(
|
|
StorageError.VALIDATION_ERROR,
|
|
'Invalid parameters',
|
|
400
|
|
);
|
|
}
|
|
|
|
// Validate key format (prevent path traversal)
|
|
if (key.includes('..') || key.includes('//')) {
|
|
return createErrorResponse(
|
|
StorageError.VALIDATION_ERROR,
|
|
'Invalid file path',
|
|
400,
|
|
{ key }
|
|
);
|
|
}
|
|
|
|
console.log('Fetching file content from S3:', { key });
|
|
|
|
try {
|
|
// Get the file content
|
|
const content = await getObjectContent(key);
|
|
|
|
if (!content) {
|
|
return createErrorResponse(
|
|
StorageError.NOT_FOUND,
|
|
'File not found',
|
|
404,
|
|
{ key }
|
|
);
|
|
}
|
|
|
|
return NextResponse.json({ content });
|
|
} catch (s3Error) {
|
|
console.error('S3 error fetching file content:', s3Error);
|
|
// Check if file doesn't exist
|
|
if (s3Error instanceof Error && s3Error.message.includes('NoSuchKey')) {
|
|
return createErrorResponse(
|
|
StorageError.NOT_FOUND,
|
|
'File not found',
|
|
404,
|
|
{ key }
|
|
);
|
|
}
|
|
return createErrorResponse(
|
|
StorageError.S3_ERROR,
|
|
'Failed to fetch file content from storage',
|
|
503,
|
|
{
|
|
error: s3Error instanceof Error ? s3Error.message : String(s3Error),
|
|
key
|
|
}
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching file content:', error);
|
|
return createErrorResponse(
|
|
StorageError.INTERNAL_ERROR,
|
|
'An unexpected error occurred',
|
|
500,
|
|
{ error: error instanceof Error ? error.message : String(error) }
|
|
);
|
|
}
|
|
}
|