Skip to content

Commit cee37fe

Browse files
authored
Merge pull request #73 from CoderRC/main
Improved the Download Share and Delete to show failure less times in the Locations of the Local Files.
2 parents b202ab0 + e5a089c commit cee37fe

File tree

6 files changed

+241
-18
lines changed

6 files changed

+241
-18
lines changed

src/hooks/useFileOperations.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,25 @@ export const useFileOperations = () => {
6666
}
6767
}, []);
6868

69+
const downloadFile = useCallback(async (filename) => {
70+
setIsLoading(true);
71+
try {
72+
const res = await fetch(`/api/files/download?file=${encodeURIComponent(filename)}`);
73+
if (res.ok) {
74+
return await res.text();
75+
}
76+
throw new Error('Failed to download file');
77+
} finally {
78+
setIsLoading(false);
79+
}
80+
}, []);
81+
6982
return {
7083
createFile,
7184
updateFile,
7285
deleteFile,
7386
getFiles,
87+
downloadFile,
7488
isLoading,
7589
};
7690
};

src/pages/api/files/[id].js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// src/pages/api/files/[id].js
2+
import dbConnect from '@/lib/mongodb';
3+
import { verifyToken } from '@/lib/auth';
4+
import File from '@/models/File';
5+
6+
export default async function handler(req, res) {
7+
await dbConnect();
8+
9+
if (req.method !== 'DELETE') {
10+
return res.status(405).json({ error: 'Method not allowed' });
11+
}
12+
13+
try {
14+
const user = await verifyToken(req);
15+
const { id } = req.query;
16+
17+
if (!id) {
18+
return res.status(400).json({ error: 'File ID is required' });
19+
}
20+
21+
// Find the file
22+
const file = await File.findById(id);
23+
if (!file) {
24+
return res.status(404).json({ error: 'File not found' });
25+
}
26+
27+
// Check if user is the owner
28+
if (file.owner.toString() === user._id.toString()) {
29+
// User is owner - delete the file completely
30+
await File.findByIdAndDelete(id);
31+
return res.status(200).json({
32+
message: 'File deleted successfully',
33+
deletedFile: file
34+
});
35+
}
36+
37+
// Check if user is a collaborator
38+
const collaboratorIndex = file.collaborators.findIndex(
39+
collab => collab.user.toString() === user._id.toString()
40+
);
41+
42+
if (collaboratorIndex !== -1) {
43+
// User is collaborator - remove them from collaborators (unshare)
44+
file.collaborators.splice(collaboratorIndex, 1);
45+
await file.save();
46+
47+
return res.status(200).json({
48+
message: 'File unshared successfully',
49+
unsharedFile: file
50+
});
51+
}
52+
53+
// User has no access to the file
54+
return res.status(403).json({ error: 'Access denied' });
55+
56+
} catch (error) {
57+
console.error('Delete error:', error);
58+
res.status(500).json({ error: 'Failed to delete file' });
59+
}
60+
}

src/pages/api/files/database.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,21 @@ export default async function handler(req, res) {
105105
}
106106

107107
if (req.method === 'GET') {
108-
const files = Array.from(fileDatabase.values());
109-
console.log('Getting files, database size:', fileDatabase.size);
110-
res.status(200).json(files);
108+
const { id } = req.query;
109+
110+
if (id) {
111+
// Get specific file by ID
112+
const file = fileDatabase.get(id);
113+
if (!file) {
114+
return res.status(404).json({ error: 'File not found' });
115+
}
116+
res.status(200).json(file);
117+
} else {
118+
// Get all files
119+
const files = Array.from(fileDatabase.values());
120+
console.log('Getting files, database size:', fileDatabase.size);
121+
res.status(200).json(files);
122+
}
111123
} else if (req.method === 'POST') {
112124
const { name, content, type } = req.body;
113125
const fileId = `file_${Date.now()}`;

src/pages/api/files/download.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// src/pages/api/files/download.js
2+
import dbConnect from '@/lib/mongodb';
3+
import { verifyToken } from '@/lib/auth';
4+
5+
// Simple in-memory storage for files (replace with your actual database logic)
6+
let fileStorage = new Map();
7+
8+
export default async function handler(req, res) {
9+
if (req.method !== 'GET') {
10+
return res.status(405).json({ error: 'Method not allowed' });
11+
}
12+
13+
try {
14+
await dbConnect();
15+
const user = await verifyToken(req);
16+
const { file: filename } = req.query;
17+
18+
if (!filename) {
19+
return res.status(400).json({ error: 'Filename is required' });
20+
}
21+
22+
// Get files from the database endpoint
23+
const dbResponse = await fetch(`${getBaseUrl(req)}/api/files/database`, {
24+
headers: {
25+
'Cookie': req.headers.cookie || ''
26+
}
27+
});
28+
29+
if (!dbResponse.ok) {
30+
throw new Error('Failed to fetch files from database');
31+
}
32+
33+
const files = await dbResponse.json();
34+
const file = files.find(f => f.name === filename);
35+
36+
if (!file) {
37+
return res.status(404).json({ error: 'File not found' });
38+
}
39+
40+
// Set headers for file download
41+
res.setHeader('Content-Type', 'text/plain');
42+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
43+
44+
// Send file content
45+
res.status(200).send(file.content || '');
46+
} catch (error) {
47+
console.error('Download error:', error);
48+
49+
// Fallback: try to get file from localStorage data
50+
try {
51+
const files = JSON.parse(localStorage.getItem('orbitos-files') || '[]');
52+
const file = files.find(f => f.name === filename);
53+
54+
if (file) {
55+
res.setHeader('Content-Type', 'text/plain');
56+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
57+
return res.status(200).send(file.content || '');
58+
}
59+
} catch (fallbackError) {
60+
console.error('Fallback download also failed:', fallbackError);
61+
}
62+
63+
res.status(500).json({ error: 'Failed to download file' });
64+
}
65+
}
66+
67+
function getBaseUrl(req) {
68+
const host = req.headers.host;
69+
const protocol = req.headers['x-forwarded-proto'] || 'http';
70+
return `${protocol}://${host}`;
71+
}

src/pages/api/files/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export default async function handler(req, res) {
1111
try {
1212
const files = await File.find({
1313
$or: [{ owner: user._id }, { 'collaborators.user': user._id }],
14-
}).populate('owner', 'username');
14+
}).populate('owner', 'username _id'); // Include _id in populate
15+
1516
res.json({ files });
1617
} catch (error) {
1718
res.status(500).json({ error: 'Failed to fetch files' });

src/pages/apps/filemanager.js

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import React, { useState, useEffect } from 'react';
44
import { useTheme } from '@/context/ThemeContext';
55
import { useDrive } from '@/context/DriveContext';
6+
import { useAuth } from '@/context/AuthContext';
67

7-
const FileItem = ({ item, source, theme, onDownload, onShare, onDelete }) => {
8+
const FileItem = ({ item, source, theme, onDownload, onShare, onDelete, currentUserId }) => {
89
const isGdrive = source === 'gdrive';
910
const icon = isGdrive ? (
1011
<img src={item.iconLink} alt="" className="w-5 h-5" />
@@ -17,6 +18,9 @@ const FileItem = ({ item, source, theme, onDownload, onShare, onDelete }) => {
1718
// Get the correct file ID - use item.id for Google Drive, item._id for local files
1819
const fileId = item.id || item._id;
1920

21+
// Check if current user is the owner (for local files)
22+
const isOwner = !isGdrive && item.owner && item.owner._id === currentUserId;
23+
2024
return (
2125
<li
2226
className={`flex justify-between items-center p-2 rounded ${theme.app.button_subtle_hover}`}
@@ -29,6 +33,9 @@ const FileItem = ({ item, source, theme, onDownload, onShare, onDelete }) => {
2933
>
3034
{item.name}
3135
{isDirectory ? '/' : ''}
36+
{!isGdrive && !isOwner && (
37+
<span className="text-xs text-gray-500 ml-2">(Shared)</span>
38+
)}
3239
</span>
3340
</div>
3441
<div className="flex gap-2">
@@ -59,11 +66,15 @@ const FileItem = ({ item, source, theme, onDownload, onShare, onDelete }) => {
5966
Share
6067
</button>
6168
<button
62-
onClick={() => onDelete(fileId)} // Also fix delete
63-
className="px-2 py-1 text-sm rounded bg-red-500 text-white hover:bg-red-600"
64-
title="Delete File"
69+
onClick={() => onDelete(fileId, item.name, isOwner)} // Also fixed delete
70+
className={`px-2 py-1 text-sm rounded ${
71+
isOwner
72+
? 'bg-red-500 hover:bg-red-600 text-white'
73+
: 'bg-orange-500 hover:bg-orange-600 text-white'
74+
}`}
75+
title={isOwner ? 'Delete File' : 'Unshare File'}
6576
>
66-
Delete
77+
{isOwner ? 'Delete' : 'Unshare'}
6778
</button>
6879
</>
6980
)
@@ -81,6 +92,7 @@ const StatusMessage = ({ children }) => (
8192

8293
export default function FileManagerApp() {
8394
const { theme } = useTheme();
95+
const { user: currentUser } = useAuth(); // Get current user from auth context
8496
const [activeSource, setActiveSource] = useState('gdrive');
8597
const [localItems, setLocalItems] = useState([]);
8698
const [isLoadingLocal, setIsLoadingLocal] = useState(true);
@@ -116,7 +128,8 @@ export default function FileManagerApp() {
116128
const data = await res.json();
117129
setLocalItems(data.files || []);
118130
} catch (error) {
119-
setError('Failed to load files.');
131+
console.error('Failed to load files:', error);
132+
setError('Failed to load files from server.');
120133
setLocalItems([]);
121134
} finally {
122135
setIsLoadingLocal(false);
@@ -151,9 +164,42 @@ export default function FileManagerApp() {
151164
}
152165
};
153166

154-
const handleDownload = (filename) => {
155-
const url = `/api/files/download?file=${encodeURIComponent(filename)}`;
156-
window.open(url, '_blank');
167+
const handleDownload = async (filename) => {
168+
try {
169+
// Use the same method as Notes app - fetch from /api/files
170+
const response = await fetch('/api/files');
171+
172+
if (!response.ok) {
173+
throw new Error(`Download failed: ${response.status}`);
174+
}
175+
176+
const data = await response.json();
177+
const files = data.files || [];
178+
const file = files.find(f => f.name === filename);
179+
180+
if (!file) {
181+
throw new Error(`File "${filename}" not found`);
182+
}
183+
184+
const content = file.content || '';
185+
186+
// Create download
187+
const blob = new Blob([content], { type: 'text/plain' });
188+
const downloadUrl = URL.createObjectURL(blob);
189+
190+
const link = document.createElement('a');
191+
link.href = downloadUrl;
192+
link.download = filename;
193+
document.body.appendChild(link);
194+
link.click();
195+
document.body.removeChild(link);
196+
URL.revokeObjectURL(downloadUrl);
197+
198+
showNotification(`Downloaded "${filename}" successfully`, 'success');
199+
} catch (error) {
200+
console.error('Download failed:', error);
201+
showNotification(`Download failed: ${error.message}`, 'error');
202+
}
157203
};
158204

159205
const handleShareFile = async (fileId, userEmail, permission) => {
@@ -179,16 +225,34 @@ export default function FileManagerApp() {
179225
showNotification(error.message, 'error');
180226
}
181227
};
228+
229+
const handleDelete = async (fileId, filename, isOwner) => {
230+
if (!confirm(`Are you sure you want to ${isOwner ? 'delete' : 'unshare'} "${filename}"?`)) {
231+
return;
232+
}
182233

183-
const handleDelete = async (fileId) => {
184234
try {
185-
const res = await fetch(`/api/files/${fileId}`, { method: 'DELETE' });
186-
if (!res.ok) throw new Error('Delete failed');
235+
const response = await fetch(`/api/files/${fileId}`, {
236+
method: 'DELETE',
237+
headers: {
238+
'Content-Type': 'application/json',
239+
},
240+
});
241+
242+
if (!response.ok) {
243+
const errorData = await response.json();
244+
throw new Error(errorData.error || `Failed to ${isOwner ? 'delete' : 'unshare'} file`);
245+
}
246+
247+
const result = await response.json();
248+
249+
setLocalItems(prev => prev.filter(item => (item.id !== fileId && item._id !== fileId)));
250+
251+
showNotification(result.message, 'success');
187252
await fetchLocalItems();
188-
showNotification('File deleted successfully', 'success');
189253
} catch (error) {
190254
console.error('Delete file failed:', error);
191-
showNotification('File deletion failed', 'error');
255+
showNotification(error.message, 'error');
192256
}
193257
};
194258

@@ -265,6 +329,7 @@ export default function FileManagerApp() {
265329
onDownload={handleDownload}
266330
onShare={handleShareClick}
267331
onDelete={handleDelete}
332+
currentUserId={currentUser?.id}
268333
/>
269334
))}
270335
</ul>

0 commit comments

Comments
 (0)