Skip to content

Commit 6049738

Browse files
committed
File page
1 parent 569fcb4 commit 6049738

File tree

2 files changed

+88
-9
lines changed

2 files changed

+88
-9
lines changed

islands/Decrypt.tsx

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// islands/Decrypt.tsx
22
import { useEffect, useState } from 'preact/hooks'
33

4-
export default function Decrypt() {
4+
interface DecryptProps {
5+
fileExists: boolean | undefined
6+
}
7+
8+
export default function Decrypt({ fileExists }: DecryptProps) {
59
const [status, setStatus] = useState('')
610
const [error, setError] = useState('')
711
const [downloaded, setDownloaded] = useState(false)
@@ -15,6 +19,10 @@ export default function Decrypt() {
1519
return
1620
}
1721

22+
if (fileExists === false) {
23+
throw new Error('File not found or expired')
24+
}
25+
1826
// Extract data from hash
1927
const key = hash.slice(0, 64)
2028
const iv = hash.slice(64, 96)
@@ -23,8 +31,8 @@ export default function Decrypt() {
2331

2432
setStatus('Decrypting file...')
2533

26-
// Fetch encrypted file using key as filename
27-
const response = await fetch(`/${key}.enc`)
34+
// Get file from backend using the current URL path
35+
const response = await fetch(`/${key}`)
2836
if (!response.ok) {
2937
throw new Error(
3038
response.status === 404
@@ -93,7 +101,7 @@ export default function Decrypt() {
93101
const key = hash.slice(0, 64)
94102
const iv = hash.slice(64, 96)
95103

96-
const response = await fetch(`/${key}.enc`, {
104+
const response = await fetch(`/${key}`, {
97105
method: 'DELETE',
98106
headers: {
99107
'Authorization': `Bearer ${key}${iv}`,
@@ -102,7 +110,7 @@ export default function Decrypt() {
102110

103111
if (!response.ok) throw new Error('Delete failed')
104112
setStatus('✓ File deleted')
105-
setDownloaded(false) // Hide delete button after successful deletion
113+
setDownloaded(false)
106114
} catch (_err) {
107115
setError('Failed to delete file')
108116
}

routes/d/[hash].tsx

+75-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,69 @@
11
// routes/d/[hash].tsx
22
import { Head } from '$fresh/runtime.ts'
3-
import Decrypt from '@/islands/Decrypt.tsx'
3+
import { Handlers } from '$fresh/server.ts'
4+
import { recordDownload } from '../../utils/stats.ts'
5+
import Decrypt from '../../islands/Decrypt.tsx'
46

5-
export default function DecryptPage() {
7+
interface FileData {
8+
content: Uint8Array
9+
size: number
10+
created: string
11+
deletionKey: string
12+
}
13+
14+
const kv = await Deno.openKv()
15+
16+
async function getFileData(key: string): Promise<FileData | null> {
17+
const res = await kv.get<FileData>(['files', key])
18+
return res.value
19+
}
20+
21+
// This handler will serve the file content
22+
export const handler: Handlers = {
23+
async GET(req, ctx) {
24+
const { hash } = ctx.params
25+
const key = hash.slice(0, 64) // First 64 chars are the key
26+
27+
// Check if file exists
28+
const fileData = await getFileData(key)
29+
if (!fileData) {
30+
// Fall back to UI to show error if file not found
31+
return ctx.render()
32+
}
33+
34+
// If direct file request, serve file content
35+
const url = new URL(req.url)
36+
if (url.searchParams.has('download')) {
37+
await recordDownload(fileData.size)
38+
39+
const stream = new ReadableStream({
40+
start(controller) {
41+
controller.enqueue(fileData.content)
42+
controller.close()
43+
},
44+
})
45+
46+
return new Response(stream, {
47+
headers: {
48+
'content-type': 'application/octet-stream',
49+
'cache-control': 'no-store',
50+
'content-length': fileData.size.toString(),
51+
},
52+
})
53+
}
54+
55+
// Otherwise render the UI
56+
return ctx.render({
57+
fileExists: true,
58+
created: fileData.created,
59+
size: fileData.size,
60+
})
61+
},
62+
}
63+
64+
export default function DecryptPage(
65+
props: { data: { fileExists?: boolean; created?: string; size?: number } },
66+
) {
667
return (
768
<>
869
<Head>
@@ -12,10 +73,20 @@ export default function DecryptPage() {
1273
<div class='max-w-md w-full space-y-8'>
1374
<div>
1475
<h1 class='mt-6 text-center text-3xl font-extrabold text-gray-900'>
15-
Decrypting your file
76+
{props.data?.fileExists ? 'Decrypt your file' : 'File not found'}
1677
</h1>
78+
{props.data?.fileExists && props.data?.created && (
79+
<p class='mt-2 text-center text-sm text-gray-600'>
80+
Uploaded {new Date(props.data.created).toLocaleString()}
81+
</p>
82+
)}
83+
{props.data?.fileExists && props.data?.size && (
84+
<p class='mt-2 text-center text-sm text-gray-600'>
85+
Size: {(props.data.size / 1024).toFixed(1)} KB
86+
</p>
87+
)}
1788
</div>
18-
<Decrypt />
89+
<Decrypt fileExists={props.data?.fileExists} />
1990
</div>
2091
</div>
2192
</>

0 commit comments

Comments
 (0)