summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--frontend/app/api/logout/route.ts4
-rw-r--r--frontend/app/api/upload/route.ts4
-rw-r--r--frontend/components/drive/DriveDirectoryClient.tsx88
-rw-r--r--frontend/components/drive/DriveDirectoryView.tsx7
-rw-r--r--frontend/components/drive/StorageUsage.tsx9
-rw-r--r--frontend/components/history/HistoryView.tsx9
-rw-r--r--frontend/lib/constants.ts37
-rw-r--r--frontend/lib/drive_server.ts83
-rw-r--r--frontend/package.json5
9 files changed, 67 insertions, 179 deletions
diff --git a/frontend/app/api/logout/route.ts b/frontend/app/api/logout/route.ts
index 51de324..202a8b7 100644
--- a/frontend/app/api/logout/route.ts
+++ b/frontend/app/api/logout/route.ts
@@ -10,7 +10,7 @@ export async function POST(request: NextRequest) {
10 10
11 if (sessionCookie) { 11 if (sessionCookie) {
12 // Call tinyauth logout endpoint to invalidate the session 12 // Call tinyauth logout endpoint to invalidate the session
13 const logoutResponse = await fetch(`${Auth_tinyauth_endpoint()}/auth/logout`, { 13 await fetch(`${Auth_tinyauth_endpoint()}/auth/logout`, {
14 method: 'POST', 14 method: 'POST',
15 headers: { 15 headers: {
16 'Cookie': `${sessionCookie.name}=${sessionCookie.value}` 16 'Cookie': `${sessionCookie.name}=${sessionCookie.value}`
@@ -29,4 +29,4 @@ export async function POST(request: NextRequest) {
29 // Even if logout fails, redirect to home 29 // Even if logout fails, redirect to home
30 return redirect('/'); 30 return redirect('/');
31 } 31 }
32} \ No newline at end of file 32}
diff --git a/frontend/app/api/upload/route.ts b/frontend/app/api/upload/route.ts
index 5ce0640..ba2d151 100644
--- a/frontend/app/api/upload/route.ts
+++ b/frontend/app/api/upload/route.ts
@@ -1,5 +1,5 @@
1import { NextRequest, NextResponse } from 'next/server' 1import { NextRequest, NextResponse } from 'next/server'
2import { writeFile, unlink } from 'fs/promises' 2import { writeFile } from 'fs/promises'
3import { tmpdir } from 'os' 3import { tmpdir } from 'os'
4import { join } from 'path' 4import { join } from 'path'
5import { randomUUID } from 'crypto' 5import { randomUUID } from 'crypto'
@@ -25,7 +25,6 @@ export async function POST(request: NextRequest) {
25 const url = new URL(request.url) 25 const url = new URL(request.url)
26 const rawPath = url.searchParams.get('path') || '/' 26 const rawPath = url.searchParams.get('path') || '/'
27 const targetPath = decodeURIComponent(rawPath) 27 const targetPath = decodeURIComponent(rawPath)
28 console.log(`targetPath = ${targetPath}`)
29 28
30 // Handle multipart form data 29 // Handle multipart form data
31 const formData = await request.formData() 30 const formData = await request.formData()
@@ -53,7 +52,6 @@ export async function POST(request: NextRequest) {
53 52
54 // Construct the final drive path 53 // Construct the final drive path
55 const finalPath = `${targetPath}/${file.name}` 54 const finalPath = `${targetPath}/${file.name}`
56 console.log(`finalPath = ${finalPath}`)
57 55
58 // Import file using Drive_import (uses --mode move, so temp file is automatically deleted) 56 // Import file using Drive_import (uses --mode move, so temp file is automatically deleted)
59 await Drive_import(tempFilePath, finalPath, user.email) 57 await Drive_import(tempFilePath, finalPath, user.email)
diff --git a/frontend/components/drive/DriveDirectoryClient.tsx b/frontend/components/drive/DriveDirectoryClient.tsx
index f523e4f..d238065 100644
--- a/frontend/components/drive/DriveDirectoryClient.tsx
+++ b/frontend/components/drive/DriveDirectoryClient.tsx
@@ -32,18 +32,10 @@ import { Checkbox } from "@/components/ui/checkbox"
32import { toast } from "@/hooks/use-toast" 32import { toast } from "@/hooks/use-toast"
33import { DriveLsEntry } from "@/lib/drive_types" 33import { DriveLsEntry } from "@/lib/drive_types"
34import { UPLOAD_MAX_FILE_SIZE, UPLOAD_MAX_FILES } from "@/lib/constants" 34import { UPLOAD_MAX_FILE_SIZE, UPLOAD_MAX_FILES } from "@/lib/constants"
35import { formatFileSize } from "@/lib/utils"
35import { DriveMoveDialog } from "./DriveMoveDialog" 36import { DriveMoveDialog } from "./DriveMoveDialog"
36import { StorageUsage } from "./StorageUsage" 37import { StorageUsage } from "./StorageUsage"
37import type { StorageData } from "@/lib/storage" 38import type { StorageData } from "@/lib/storage"
38import type { UserAuth } from "@/lib/auth_types"
39
40function formatFileSize(bytes: number): string {
41 if (bytes === 0) return "0 Bytes"
42 const k = 1024
43 const sizes = ["Bytes", "KB", "MB", "GB"]
44 const i = Math.floor(Math.log(bytes) / Math.log(k))
45 return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
46}
47 39
48function formatDate(timestamp: number): string { 40function formatDate(timestamp: number): string {
49 return new Date(timestamp * 1000).toISOString().split('T')[0] 41 return new Date(timestamp * 1000).toISOString().split('T')[0]
@@ -52,9 +44,9 @@ function formatDate(timestamp: number): string {
52function formatDateTime(timestamp: number): string { 44function formatDateTime(timestamp: number): string {
53 const date = new Date(timestamp * 1000) 45 const date = new Date(timestamp * 1000)
54 const dateStr = date.toISOString().split('T')[0] 46 const dateStr = date.toISOString().split('T')[0]
55 const timeStr = date.toLocaleTimeString('en-US', { 47 const timeStr = date.toLocaleTimeString('en-US', {
56 hour12: false, 48 hour12: false,
57 hour: '2-digit', 49 hour: '2-digit',
58 minute: '2-digit', 50 minute: '2-digit',
59 second: '2-digit' 51 second: '2-digit'
60 }) 52 })
@@ -72,10 +64,9 @@ interface DriveDirectoryClientProps {
72 files: DriveLsEntry[] 64 files: DriveLsEntry[]
73 breadcrumbs: Breadcrumb[] 65 breadcrumbs: Breadcrumb[]
74 storageData: StorageData 66 storageData: StorageData
75 user: UserAuth
76} 67}
77 68
78export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, user }: DriveDirectoryClientProps) { 69export function DriveDirectoryClient({ path, files, breadcrumbs, storageData }: DriveDirectoryClientProps) {
79 const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set()) 70 const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set())
80 const [renameDialogOpen, setRenameDialogOpen] = useState(false) 71 const [renameDialogOpen, setRenameDialogOpen] = useState(false)
81 const [infoDialogOpen, setInfoDialogOpen] = useState(false) 72 const [infoDialogOpen, setInfoDialogOpen] = useState(false)
@@ -98,10 +89,6 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
98 setSelectedFiles(newSelected) 89 setSelectedFiles(newSelected)
99 } 90 }
100 91
101 const selectAll = () => {
102 setSelectedFiles(new Set(files.map(file => file.path)))
103 }
104
105 const deselectAll = () => { 92 const deselectAll = () => {
106 setSelectedFiles(new Set()) 93 setSelectedFiles(new Set())
107 } 94 }
@@ -130,7 +117,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
130 }) 117 })
131 return 118 return
132 } 119 }
133 120
134 const filename = item.path.split('/').pop() || 'download' 121 const filename = item.path.split('/').pop() || 'download'
135 const permalink = `${window.location.origin}/blob/${item.blob}?filename=${encodeURIComponent(filename)}` 122 const permalink = `${window.location.origin}/blob/${item.blob}?filename=${encodeURIComponent(filename)}`
136 navigator.clipboard.writeText(permalink).then(() => { 123 navigator.clipboard.writeText(permalink).then(() => {
@@ -171,8 +158,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
171 title: "Renamed successfully", 158 title: "Renamed successfully",
172 description: result.message, 159 description: result.message,
173 }) 160 })
174 161
175 // Refresh page to show changes
176 window.location.reload() 162 window.location.reload()
177 } else { 163 } else {
178 throw new Error(result.error || `Rename failed with status ${response.status}`) 164 throw new Error(result.error || `Rename failed with status ${response.status}`)
@@ -222,7 +208,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
222 try { 208 try {
223 const formData = new FormData() 209 const formData = new FormData()
224 formData.append('file', file) 210 formData.append('file', file)
225 211
226 // Use the new simple upload endpoint with path as query parameter 212 // Use the new simple upload endpoint with path as query parameter
227 const response = await fetch(`/api/upload?path=${encodeURIComponent(path)}`, { 213 const response = await fetch(`/api/upload?path=${encodeURIComponent(path)}`, {
228 method: 'POST', 214 method: 'POST',
@@ -247,8 +233,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
247 title: "Upload successful", 233 title: "Upload successful",
248 description: `${successCount} file(s) uploaded successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}` 234 description: `${successCount} file(s) uploaded successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`
249 }) 235 })
250 236
251 // Refresh page to show changes
252 window.location.reload() 237 window.location.reload()
253 } 238 }
254 239
@@ -292,8 +277,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
292 title: "Deleted successfully", 277 title: "Deleted successfully",
293 description: result.message, 278 description: result.message,
294 }) 279 })
295 280
296 // Refresh page to show changes
297 window.location.reload() 281 window.location.reload()
298 } else { 282 } else {
299 throw new Error(result.error || `Delete failed with status ${response.status}`) 283 throw new Error(result.error || `Delete failed with status ${response.status}`)
@@ -322,7 +306,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
322 // Extract filename from the full path 306 // Extract filename from the full path
323 const fileName = filePath.split('/').pop() || '' 307 const fileName = filePath.split('/').pop() || ''
324 // Construct new path: destination + filename 308 // Construct new path: destination + filename
325 const newPath = destinationPath.endsWith('/') 309 const newPath = destinationPath.endsWith('/')
326 ? `${destinationPath}${fileName}` 310 ? `${destinationPath}${fileName}`
327 : `${destinationPath}/${fileName}` 311 : `${destinationPath}/${fileName}`
328 312
@@ -360,14 +344,13 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
360 title: "Move completed", 344 title: "Move completed",
361 description: `${successCount} item(s) moved successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`, 345 description: `${successCount} item(s) moved successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`,
362 }) 346 })
363 347
364 // Refresh page to show changes
365 window.location.reload() 348 window.location.reload()
366 } 349 }
367 350
368 if (errorCount > 0 && successCount === 0) { 351 if (errorCount > 0 && successCount === 0) {
369 toast({ 352 toast({
370 title: "Move failed", 353 title: "Move failed",
371 description: `All ${errorCount} item(s) failed to move. ${errors[0] || ''}`, 354 description: `All ${errorCount} item(s) failed to move. ${errors[0] || ''}`,
372 variant: "destructive" 355 variant: "destructive"
373 }) 356 })
@@ -416,8 +399,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
416 title: "Folder created", 399 title: "Folder created",
417 description: result.message, 400 description: result.message,
418 }) 401 })
419 402
420 // Refresh page to show changes
421 window.location.reload() 403 window.location.reload()
422 } else { 404 } else {
423 throw new Error(result.error || `Failed to create folder`) 405 throw new Error(result.error || `Failed to create folder`)
@@ -447,7 +429,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
447 {index === breadcrumbs.length - 1 ? ( 429 {index === breadcrumbs.length - 1 ? (
448 <span className="text-foreground font-medium">{crumb.name}</span> 430 <span className="text-foreground font-medium">{crumb.name}</span>
449 ) : ( 431 ) : (
450 <Link 432 <Link
451 href={crumb.path} 433 href={crumb.path}
452 className="hover:text-foreground transition-colors" 434 className="hover:text-foreground transition-colors"
453 > 435 >
@@ -457,9 +439,9 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
457 </div> 439 </div>
458 ))} 440 ))}
459 </nav> 441 </nav>
460 442
461 <div className="flex items-center gap-2 sm:gap-2"> 443 <div className="flex items-center gap-2 sm:gap-2">
462 <Button 444 <Button
463 variant="secondary" 445 variant="secondary"
464 onClick={() => setCreateFolderDialogOpen(true)} 446 onClick={() => setCreateFolderDialogOpen(true)}
465 className="flex-1 sm:flex-none" 447 className="flex-1 sm:flex-none"
@@ -467,7 +449,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
467 <FolderPlus className="mr-2 h-4 w-4" /> 449 <FolderPlus className="mr-2 h-4 w-4" />
468 Create Folder 450 Create Folder
469 </Button> 451 </Button>
470 <Button 452 <Button
471 onClick={() => fileInputRef.current?.click()} 453 onClick={() => fileInputRef.current?.click()}
472 disabled={uploading} 454 disabled={uploading}
473 className="flex-1 sm:flex-none" 455 className="flex-1 sm:flex-none"
@@ -534,16 +516,16 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
534 files.map((file) => { 516 files.map((file) => {
535 const isSelected = selectedFiles.has(file.path) 517 const isSelected = selectedFiles.has(file.path)
536 const fileName = file.path.split('/').pop() || file.path 518 const fileName = file.path.split('/').pop() || file.path
537 519
538 return ( 520 return (
539 <TableRow 521 <TableRow
540 key={file.path} 522 key={file.path}
541 className={`hover:bg-muted/50 ${isSelected ? "bg-muted/30" : ""}`} 523 className={`hover:bg-muted/50 ${isSelected ? "bg-muted/30" : ""}`}
542 > 524 >
543 <TableCell className="w-[40px]" onClick={(e) => e.stopPropagation()}> 525 <TableCell className="w-[40px]" onClick={(e) => e.stopPropagation()}>
544 <Checkbox 526 <Checkbox
545 checked={isSelected} 527 checked={isSelected}
546 onCheckedChange={() => toggleFileSelection(file.path)} 528 onCheckedChange={() => toggleFileSelection(file.path)}
547 /> 529 />
548 </TableCell> 530 </TableCell>
549 <TableCell className="font-medium"> 531 <TableCell className="font-medium">
@@ -552,7 +534,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
552 <> 534 <>
553 <Folder className="h-4 w-4 text-blue-500 flex-shrink-0" /> 535 <Folder className="h-4 w-4 text-blue-500 flex-shrink-0" />
554 <div className="min-w-0 max-w-[60vw]"> 536 <div className="min-w-0 max-w-[60vw]">
555 <Link 537 <Link
556 href={`/drive${file.path}`} 538 href={`/drive${file.path}`}
557 className="text-blue-600 hover:text-blue-800 hover:underline cursor-pointer block truncate" 539 className="text-blue-600 hover:text-blue-800 hover:underline cursor-pointer block truncate"
558 title={fileName} 540 title={fileName}
@@ -606,8 +588,8 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
606 Info 588 Info
607 </DropdownMenuItem> 589 </DropdownMenuItem>
608 <DropdownMenuSeparator /> 590 <DropdownMenuSeparator />
609 <DropdownMenuItem 591 <DropdownMenuItem
610 onClick={() => handleDelete([file.path])} 592 onClick={() => handleDelete([file.path])}
611 className="text-red-600" 593 className="text-red-600"
612 > 594 >
613 <Trash2 className="mr-2 h-4 w-4" /> 595 <Trash2 className="mr-2 h-4 w-4" />
@@ -695,7 +677,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
695 </div> 677 </div>
696 <div> 678 <div>
697 <Label className="text-sm font-medium text-muted-foreground">Path</Label> 679 <Label className="text-sm font-medium text-muted-foreground">Path</Label>
698 <p className="text-sm font-mono text-xs">{currentItem.path}</p> 680 <p className="font-mono text-xs">{currentItem.path}</p>
699 </div> 681 </div>
700 </div> 682 </div>
701 <div className="flex justify-end"> 683 <div className="flex justify-end">
@@ -749,13 +731,13 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
749 </DialogContent> 731 </DialogContent>
750 </Dialog> 732 </Dialog>
751 733
752 <input 734 <input
753 ref={fileInputRef} 735 ref={fileInputRef}
754 type="file" 736 type="file"
755 multiple 737 multiple
756 className="hidden" 738 className="hidden"
757 onChange={handleFileUpload} 739 onChange={handleFileUpload}
758 /> 740 />
759 </div> 741 </div>
760 ) 742 )
761} \ No newline at end of file 743}
diff --git a/frontend/components/drive/DriveDirectoryView.tsx b/frontend/components/drive/DriveDirectoryView.tsx
index d2f30ee..de9d70a 100644
--- a/frontend/components/drive/DriveDirectoryView.tsx
+++ b/frontend/components/drive/DriveDirectoryView.tsx
@@ -2,7 +2,6 @@ import { DriveLsEntry } from "@/lib/drive_types"
2import { DriveDirectoryClient } from "./DriveDirectoryClient" 2import { DriveDirectoryClient } from "./DriveDirectoryClient"
3import { DriveHeader } from "./DriveHeader" 3import { DriveHeader } from "./DriveHeader"
4import type { StorageData } from "@/lib/storage" 4import type { StorageData } from "@/lib/storage"
5import { Auth_get_user } from "@/lib/auth"
6 5
7interface DriveDirectoryViewProps { 6interface DriveDirectoryViewProps {
8 path: string 7 path: string
@@ -20,7 +19,7 @@ function generateBreadcrumbs(currentPath: string) {
20 const breadcrumbs = [{ name: 'Root', path: '/drive' }] 19 const breadcrumbs = [{ name: 'Root', path: '/drive' }]
21 20
22 let accumulatedPath = '' 21 let accumulatedPath = ''
23 parts.forEach((part, index) => { 22 parts.forEach((part, _index) => {
24 accumulatedPath += '/' + part 23 accumulatedPath += '/' + part
25 breadcrumbs.push({ 24 breadcrumbs.push({
26 name: decodeURIComponent(part), // Decode URL encoded characters 25 name: decodeURIComponent(part), // Decode URL encoded characters
@@ -48,7 +47,6 @@ function sortFiles(files: DriveLsEntry[]): DriveLsEntry[] {
48export async function DriveDirectoryView({ path, files, storageData }: DriveDirectoryViewProps) { 47export async function DriveDirectoryView({ path, files, storageData }: DriveDirectoryViewProps) {
49 const sortedFiles = sortFiles(files) 48 const sortedFiles = sortFiles(files)
50 const breadcrumbs = generateBreadcrumbs(path) 49 const breadcrumbs = generateBreadcrumbs(path)
51 const user = await Auth_get_user()
52 50
53 return ( 51 return (
54 <div className="container mx-auto p-6 space-y-6"> 52 <div className="container mx-auto p-6 space-y-6">
@@ -58,8 +56,7 @@ export async function DriveDirectoryView({ path, files, storageData }: DriveDire
58 files={sortedFiles} 56 files={sortedFiles}
59 breadcrumbs={breadcrumbs} 57 breadcrumbs={breadcrumbs}
60 storageData={storageData} 58 storageData={storageData}
61 user={user}
62 /> 59 />
63 </div> 60 </div>
64 ) 61 )
65} \ No newline at end of file 62}
diff --git a/frontend/components/drive/StorageUsage.tsx b/frontend/components/drive/StorageUsage.tsx
index 2cb5d5d..39be4ba 100644
--- a/frontend/components/drive/StorageUsage.tsx
+++ b/frontend/components/drive/StorageUsage.tsx
@@ -1,17 +1,10 @@
1import type { StorageData } from "@/lib/storage" 1import type { StorageData } from "@/lib/storage"
2import { formatFileSize } from "@/lib/utils"
2 3
3interface StorageUsageProps { 4interface StorageUsageProps {
4 data: StorageData 5 data: StorageData
5} 6}
6 7
7function formatFileSize(bytes: number): string {
8 if (bytes === 0) return "0 Bytes"
9 const k = 1024
10 const sizes = ["Bytes", "KB", "MB", "GB", "TB"]
11 const i = Math.floor(Math.log(bytes) / Math.log(k))
12 return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
13}
14
15export function StorageUsage({ data }: StorageUsageProps) { 8export function StorageUsage({ data }: StorageUsageProps) {
16 // Safety check for undefined data 9 // Safety check for undefined data
17 if (!data) { 10 if (!data) {
diff --git a/frontend/components/history/HistoryView.tsx b/frontend/components/history/HistoryView.tsx
index c459275..e6d1cd1 100644
--- a/frontend/components/history/HistoryView.tsx
+++ b/frontend/components/history/HistoryView.tsx
@@ -3,6 +3,7 @@ import { DriveHeader } from "@/components/drive/DriveHeader"
3import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" 3import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
4import { Button } from "@/components/ui/button" 4import { Button } from "@/components/ui/button"
5import { ChevronLeft, ChevronRight } from "lucide-react" 5import { ChevronLeft, ChevronRight } from "lucide-react"
6import { formatFileSize } from "@/lib/utils"
6import Link from "next/link" 7import Link from "next/link"
7 8
8interface HistoryViewProps { 9interface HistoryViewProps {
@@ -13,14 +14,6 @@ interface HistoryViewProps {
13 totalEntries: number 14 totalEntries: number
14} 15}
15 16
16function formatFileSize(bytes: number): string {
17 if (bytes === 0) return "0 Bytes"
18 const k = 1024
19 const sizes = ["Bytes", "KB", "MB", "GB"]
20 const i = Math.floor(Math.log(bytes) / Math.log(k))
21 return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
22}
23
24function formatDateTime(timestamp: number): string { 17function formatDateTime(timestamp: number): string {
25 return new Date(timestamp * 1000).toLocaleString() 18 return new Date(timestamp * 1000).toLocaleString()
26} 19}
diff --git a/frontend/lib/constants.ts b/frontend/lib/constants.ts
index 173e90d..e01251f 100644
--- a/frontend/lib/constants.ts
+++ b/frontend/lib/constants.ts
@@ -1,39 +1,2 @@
1// Upload configuration constants
2export const UPLOAD_MAX_FILE_SIZE = 4096 * 1024 * 1024; // 100MB 1export const UPLOAD_MAX_FILE_SIZE = 4096 * 1024 * 1024; // 100MB
3export const UPLOAD_MAX_FILES = 10; // Maximum files per upload 2export const UPLOAD_MAX_FILES = 10; // Maximum files per upload
4export const UPLOAD_ALLOWED_TYPES = [
5 // Documents
6 'application/pdf',
7 'application/msword',
8 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
9 'application/vnd.ms-excel',
10 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
11 'application/vnd.ms-powerpoint',
12 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
13 'text/plain',
14 'text/csv',
15 // Images
16 'image/jpeg',
17 'image/png',
18 'image/gif',
19 'image/webp',
20 'image/svg+xml',
21 // Videos
22 'video/mp4',
23 'video/webm',
24 'video/ogg',
25 // Audio
26 'audio/mpeg',
27 'audio/wav',
28 'audio/ogg',
29 // Archives
30 'application/zip',
31 'application/x-rar-compressed',
32 'application/x-7z-compressed',
33 // Code/Text
34 'application/json',
35 'text/javascript',
36 'text/html',
37 'text/css',
38 'application/xml'
39]; // Empty array means all types allowed
diff --git a/frontend/lib/drive_server.ts b/frontend/lib/drive_server.ts
index e7c88e8..1462f6e 100644
--- a/frontend/lib/drive_server.ts
+++ b/frontend/lib/drive_server.ts
@@ -1,11 +1,21 @@
1import { spawnSync } from 'child_process' 1import { spawnSync } from 'child_process'
2import { DriveLsEntry, DriveLogEntry } from './drive_types' 2import { DriveLsEntry, DriveLogEntry } from './drive_types'
3import { Drive_split_path, Drive_basename } from './drive_shared'
4 3
5/** 4/**
6 * Server-only drive functions that use Node.js APIs 5 * Server-only drive functions that use Node.js APIs
7 */ 6 */
8 7
8function executeFctDriveCommand(args: string[]): string {
9 const result = spawnSync('fctdrive', args, { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 })
10 if (result.error) {
11 throw new Error(`Failed to execute fctdrive: ${result.error.message}`)
12 }
13 if (result.status !== 0) {
14 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`)
15 }
16 return result.stdout
17}
18
9/// lists the given path on the drive 19/// lists the given path on the drive
10export async function Drive_ls(path: string, recursive: boolean): Promise<DriveLsEntry[]> { 20export async function Drive_ls(path: string, recursive: boolean): Promise<DriveLsEntry[]> {
11 const args = ['ls'] 21 const args = ['ls']
@@ -16,14 +26,7 @@ export async function Drive_ls(path: string, recursive: boolean): Promise<DriveL
16 args.push(path) 26 args.push(path)
17 } 27 }
18 28
19 const result = spawnSync('fctdrive', args, { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }) 29 const stdout = executeFctDriveCommand(args)
20 if (result.error) {
21 throw new Error(`Failed to execute fctdrive: ${result.error.message}`)
22 }
23 if (result.status !== 0) {
24 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`)
25 }
26 const stdout = result.stdout
27 const entries = [] 30 const entries = []
28 for (const line of stdout.split('\n')) { 31 for (const line of stdout.split('\n')) {
29 if (line.trim() == "") 32 if (line.trim() == "")
@@ -54,70 +57,34 @@ export async function Drive_ls(path: string, recursive: boolean): Promise<DriveL
54 57
55/// import the file at local_path by moving it to drive_path 58/// import the file at local_path by moving it to drive_path
56export async function Drive_import(local_path: string, drive_path: string, email: string) { 59export async function Drive_import(local_path: string, drive_path: string, email: string) {
57 const result = spawnSync('fctdrive', ['import', local_path, "--mode", "move", "--destination", drive_path, "--email", email], { encoding: 'utf-8' }); 60 executeFctDriveCommand(['import', local_path, "--mode", "move", "--destination", drive_path, "--email", email]);
58 if (result.error) {
59 throw new Error(`Failed to execute fctdrive: ${result.error.message}`);
60 }
61 if (result.status !== 0) {
62 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`);
63 }
64} 61}
65 62
66/// returns the full filesystem path of the blob given its id 63/// returns the full filesystem path of the blob given its id
67export async function Drive_blob_path(blob: string): Promise<string> { 64export async function Drive_blob_path(blob: string): Promise<string> {
68 const result = spawnSync('fctdrive', ['blob', blob], { encoding: 'utf-8' }) 65 const stdout = executeFctDriveCommand(['blob', blob])
69 if (result.error) { 66 return stdout.trim();
70 throw new Error(`Failed to execute fctdrive: ${result.error.message}`)
71 }
72 if (result.status !== 0) {
73 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`)
74 }
75 return result.stdout.trim();
76} 67}
77 68
78/// removes the file or directory at the given path 69/// removes the file or directory at the given path
79export async function Drive_remove(path: string, email: string) { 70export async function Drive_remove(path: string, email: string) {
80 const result = spawnSync('fctdrive', ['remove', '--email', email, path], { encoding: 'utf-8' }); 71 executeFctDriveCommand(['remove', '--email', email, path]);
81 if (result.error) {
82 throw new Error(`Failed to execute fctdrive: ${result.error.message}`);
83 }
84 if (result.status !== 0) {
85 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`);
86 }
87} 72}
88 73
89/// creates a directory at the given path 74/// creates a directory at the given path
90export async function Drive_mkdir(path: string, email: string) { 75export async function Drive_mkdir(path: string, email: string) {
91 const result = spawnSync('fctdrive', ['mkdir', '--email', email, '--path', path], { encoding: 'utf-8' }); 76 executeFctDriveCommand(['mkdir', '--email', email, '--path', path]);
92 if (result.error) {
93 throw new Error(`Failed to execute fctdrive: ${result.error.message}`);
94 }
95 if (result.status !== 0) {
96 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`);
97 }
98} 77}
99 78
100/// renames a file or directory from old path to new path 79/// renames a file or directory from old path to new path
101export async function Drive_rename(oldPath: string, newPath: string, email: string) { 80export async function Drive_rename(oldPath: string, newPath: string, email: string) {
102 const result = spawnSync('fctdrive', ['rename', '--email', email, '--old', oldPath, '--new', newPath], { encoding: 'utf-8' }); 81 executeFctDriveCommand(['rename', '--email', email, '--old', oldPath, '--new', newPath]);
103 if (result.error) {
104 throw new Error(`Failed to execute fctdrive: ${result.error.message}`);
105 }
106 if (result.status !== 0) {
107 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`);
108 }
109} 82}
110 83
111/// gets the blob ID for a given path 84/// gets the blob ID for a given path
112export async function Drive_stat(path: string): Promise<string> { 85export async function Drive_stat(path: string): Promise<string> {
113 const result = spawnSync('fctdrive', ['stat', path], { encoding: 'utf-8' }); 86 const stdout = executeFctDriveCommand(['stat', path]);
114 if (result.error) { 87 return stdout.trim();
115 throw new Error(`Failed to execute fctdrive: ${result.error.message}`);
116 }
117 if (result.status !== 0) {
118 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`);
119 }
120 return result.stdout.trim();
121} 88}
122 89
123 90
@@ -130,15 +97,7 @@ export async function Drive_ls_directories(path: string = '/'): Promise<DriveLsE
130 97
131/// returns the log entries from the drive 98/// returns the log entries from the drive
132export async function Drive_log(): Promise<DriveLogEntry[]> { 99export async function Drive_log(): Promise<DriveLogEntry[]> {
133 const result = spawnSync('fctdrive', ['log'], { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }) 100 const stdout = executeFctDriveCommand(['log'])
134 if (result.error) {
135 throw new Error(`Failed to execute fctdrive: ${result.error.message}`)
136 }
137 if (result.status !== 0) {
138 throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`)
139 }
140
141 const stdout = result.stdout
142 const entries: DriveLogEntry[] = [] 101 const entries: DriveLogEntry[] = []
143 for (const line of stdout.split('\n')) { 102 for (const line of stdout.split('\n')) {
144 if (line.trim() === "") 103 if (line.trim() === "")
diff --git a/frontend/package.json b/frontend/package.json
index d3762a2..3bd5610 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,7 +6,10 @@
6 "dev": "next dev --turbopack", 6 "dev": "next dev --turbopack",
7 "build": "next build", 7 "build": "next build",
8 "start": "next start", 8 "start": "next start",
9 "lint": "next lint" 9 "lint": "next lint",
10 "lint:fix": "next lint --fix",
11 "type-check": "tsc --noEmit",
12 "analyze": "ANALYZE=true next build"
10 }, 13 },
11 "dependencies": { 14 "dependencies": {
12 "@radix-ui/react-checkbox": "^1.3.2", 15 "@radix-ui/react-checkbox": "^1.3.2",