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} /> ))}