From e5a089c7bc8a0dda13f87ea9d7d44f8d5d29b18e Mon Sep 17 00:00:00 2001 From: CoderRC Date: Sat, 25 Oct 2025 18:04:35 -0400 Subject: [PATCH] Improved the Download Share and Delete to show failure less times in the Locations of the Local Files. Improved the Download Share and Delete to show failure less times in the Locations of the Local Files. --- src/hooks/useFileOperations.js | 14 +++++ src/pages/api/files/[id].js | 60 +++++++++++++++++++++ src/pages/api/files/database.js | 18 +++++-- src/pages/api/files/download.js | 71 +++++++++++++++++++++++++ src/pages/api/files/index.js | 3 +- src/pages/apps/filemanager.js | 93 ++++++++++++++++++++++++++++----- 6 files changed, 241 insertions(+), 18 deletions(-) create mode 100644 src/pages/api/files/[id].js create mode 100644 src/pages/api/files/download.js diff --git a/src/hooks/useFileOperations.js b/src/hooks/useFileOperations.js index 0ea8c35..084766b 100644 --- a/src/hooks/useFileOperations.js +++ b/src/hooks/useFileOperations.js @@ -66,11 +66,25 @@ export const useFileOperations = () => { } }, []); + const downloadFile = useCallback(async (filename) => { + setIsLoading(true); + try { + const res = await fetch(`/api/files/download?file=${encodeURIComponent(filename)}`); + if (res.ok) { + return await res.text(); + } + throw new Error('Failed to download file'); + } finally { + setIsLoading(false); + } + }, []); + return { createFile, updateFile, deleteFile, getFiles, + downloadFile, isLoading, }; }; diff --git a/src/pages/api/files/[id].js b/src/pages/api/files/[id].js new file mode 100644 index 0000000..420815d --- /dev/null +++ b/src/pages/api/files/[id].js @@ -0,0 +1,60 @@ +// src/pages/api/files/[id].js +import dbConnect from '@/lib/mongodb'; +import { verifyToken } from '@/lib/auth'; +import File from '@/models/File'; + +export default async function handler(req, res) { + await dbConnect(); + + if (req.method !== 'DELETE') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const user = await verifyToken(req); + const { id } = req.query; + + if (!id) { + return res.status(400).json({ error: 'File ID is required' }); + } + + // Find the file + const file = await File.findById(id); + if (!file) { + return res.status(404).json({ error: 'File not found' }); + } + + // Check if user is the owner + if (file.owner.toString() === user._id.toString()) { + // User is owner - delete the file completely + await File.findByIdAndDelete(id); + return res.status(200).json({ + message: 'File deleted successfully', + deletedFile: file + }); + } + + // Check if user is a collaborator + const collaboratorIndex = file.collaborators.findIndex( + collab => collab.user.toString() === user._id.toString() + ); + + if (collaboratorIndex !== -1) { + // User is collaborator - remove them from collaborators (unshare) + file.collaborators.splice(collaboratorIndex, 1); + await file.save(); + + return res.status(200).json({ + message: 'File unshared successfully', + unsharedFile: file + }); + } + + // User has no access to the file + return res.status(403).json({ error: 'Access denied' }); + + } catch (error) { + console.error('Delete error:', error); + res.status(500).json({ error: 'Failed to delete file' }); + } +} \ No newline at end of file diff --git a/src/pages/api/files/database.js b/src/pages/api/files/database.js index 34c6452..14425d6 100644 --- a/src/pages/api/files/database.js +++ b/src/pages/api/files/database.js @@ -105,9 +105,21 @@ export default async function handler(req, res) { } if (req.method === 'GET') { - const files = Array.from(fileDatabase.values()); - console.log('Getting files, database size:', fileDatabase.size); - res.status(200).json(files); + const { id } = req.query; + + if (id) { + // Get specific file by ID + const file = fileDatabase.get(id); + if (!file) { + return res.status(404).json({ error: 'File not found' }); + } + res.status(200).json(file); + } else { + // Get all files + const files = Array.from(fileDatabase.values()); + console.log('Getting files, database size:', fileDatabase.size); + res.status(200).json(files); + } } else if (req.method === 'POST') { const { name, content, type } = req.body; const fileId = `file_${Date.now()}`; diff --git a/src/pages/api/files/download.js b/src/pages/api/files/download.js new file mode 100644 index 0000000..6215c9f --- /dev/null +++ b/src/pages/api/files/download.js @@ -0,0 +1,71 @@ +// src/pages/api/files/download.js +import dbConnect from '@/lib/mongodb'; +import { verifyToken } from '@/lib/auth'; + +// Simple in-memory storage for files (replace with your actual database logic) +let fileStorage = new Map(); + +export default async function handler(req, res) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + await dbConnect(); + const user = await verifyToken(req); + const { file: filename } = req.query; + + if (!filename) { + return res.status(400).json({ error: 'Filename is required' }); + } + + // Get files from the database endpoint + const dbResponse = await fetch(`${getBaseUrl(req)}/api/files/database`, { + headers: { + 'Cookie': req.headers.cookie || '' + } + }); + + if (!dbResponse.ok) { + throw new Error('Failed to fetch files from database'); + } + + const files = await dbResponse.json(); + const file = files.find(f => f.name === filename); + + if (!file) { + return res.status(404).json({ error: 'File not found' }); + } + + // Set headers for file download + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + + // Send file content + res.status(200).send(file.content || ''); + } catch (error) { + console.error('Download error:', error); + + // Fallback: try to get file from localStorage data + try { + const files = JSON.parse(localStorage.getItem('orbitos-files') || '[]'); + const file = files.find(f => f.name === filename); + + if (file) { + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + return res.status(200).send(file.content || ''); + } + } catch (fallbackError) { + console.error('Fallback download also failed:', fallbackError); + } + + res.status(500).json({ error: 'Failed to download file' }); + } +} + +function getBaseUrl(req) { + const host = req.headers.host; + const protocol = req.headers['x-forwarded-proto'] || 'http'; + return `${protocol}://${host}`; +} \ No newline at end of file diff --git a/src/pages/api/files/index.js b/src/pages/api/files/index.js index 8e6eac5..c986cb6 100644 --- a/src/pages/api/files/index.js +++ b/src/pages/api/files/index.js @@ -11,7 +11,8 @@ export default async function handler(req, res) { try { const files = await File.find({ $or: [{ owner: user._id }, { 'collaborators.user': user._id }], - }).populate('owner', 'username'); + }).populate('owner', 'username _id'); // Include _id in populate + res.json({ files }); } catch (error) { res.status(500).json({ error: 'Failed to fetch files' }); diff --git a/src/pages/apps/filemanager.js b/src/pages/apps/filemanager.js index 187783b..3502087 100644 --- a/src/pages/apps/filemanager.js +++ b/src/pages/apps/filemanager.js @@ -3,8 +3,9 @@ import React, { useState, useEffect } from 'react'; import { useTheme } from '@/context/ThemeContext'; import { useDrive } from '@/context/DriveContext'; +import { useAuth } from '@/context/AuthContext'; -const FileItem = ({ item, source, theme, onDownload, onShare, onDelete }) => { +const FileItem = ({ item, source, theme, onDownload, onShare, onDelete, currentUserId }) => { const isGdrive = source === 'gdrive'; const icon = isGdrive ? ( @@ -17,6 +18,9 @@ const FileItem = ({ item, source, theme, onDownload, onShare, onDelete }) => { // Get the correct file ID - use item.id for Google Drive, item._id for local files const fileId = item.id || item._id; + // Check if current user is the owner (for local files) + const isOwner = !isGdrive && item.owner && item.owner._id === currentUserId; + return (
  • { > {item.name} {isDirectory ? '/' : ''} + {!isGdrive && !isOwner && ( + (Shared) + )}
    @@ -59,11 +66,15 @@ const FileItem = ({ item, source, theme, onDownload, onShare, onDelete }) => { Share ) @@ -81,6 +92,7 @@ const StatusMessage = ({ children }) => ( export default function FileManagerApp() { const { theme } = useTheme(); + const { user: currentUser } = useAuth(); // Get current user from auth context const [activeSource, setActiveSource] = useState('gdrive'); const [localItems, setLocalItems] = useState([]); const [isLoadingLocal, setIsLoadingLocal] = useState(true); @@ -116,7 +128,8 @@ export default function FileManagerApp() { const data = await res.json(); setLocalItems(data.files || []); } catch (error) { - setError('Failed to load files.'); + console.error('Failed to load files:', error); + setError('Failed to load files from server.'); setLocalItems([]); } finally { setIsLoadingLocal(false); @@ -151,9 +164,42 @@ export default function FileManagerApp() { } }; - const handleDownload = (filename) => { - const url = `/api/files/download?file=${encodeURIComponent(filename)}`; - window.open(url, '_blank'); + const handleDownload = async (filename) => { + try { + // Use the same method as Notes app - fetch from /api/files + const response = await fetch('/api/files'); + + if (!response.ok) { + throw new Error(`Download failed: ${response.status}`); + } + + const data = await response.json(); + const files = data.files || []; + const file = files.find(f => f.name === filename); + + if (!file) { + throw new Error(`File "${filename}" not found`); + } + + const content = file.content || ''; + + // Create download + const blob = new Blob([content], { type: 'text/plain' }); + const downloadUrl = URL.createObjectURL(blob); + + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(downloadUrl); + + showNotification(`Downloaded "${filename}" successfully`, 'success'); + } catch (error) { + console.error('Download failed:', error); + showNotification(`Download failed: ${error.message}`, 'error'); + } }; const handleShareFile = async (fileId, userEmail, permission) => { @@ -179,16 +225,34 @@ export default function FileManagerApp() { showNotification(error.message, 'error'); } }; + + const handleDelete = async (fileId, filename, isOwner) => { + if (!confirm(`Are you sure you want to ${isOwner ? 'delete' : 'unshare'} "${filename}"?`)) { + return; + } - const handleDelete = async (fileId) => { try { - const res = await fetch(`/api/files/${fileId}`, { method: 'DELETE' }); - if (!res.ok) throw new Error('Delete failed'); + const response = await fetch(`/api/files/${fileId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || `Failed to ${isOwner ? 'delete' : 'unshare'} file`); + } + + const result = await response.json(); + + setLocalItems(prev => prev.filter(item => (item.id !== fileId && item._id !== fileId))); + + showNotification(result.message, 'success'); await fetchLocalItems(); - showNotification('File deleted successfully', 'success'); } catch (error) { console.error('Delete file failed:', error); - showNotification('File deletion failed', 'error'); + showNotification(error.message, 'error'); } }; @@ -265,6 +329,7 @@ export default function FileManagerApp() { onDownload={handleDownload} onShare={handleShareClick} onDelete={handleDelete} + currentUserId={currentUser?.id} /> ))}