1
1
// routes/d/[hash].tsx
2
2
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'
4
6
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
+ ) {
6
67
return (
7
68
< >
8
69
< Head >
@@ -12,10 +73,20 @@ export default function DecryptPage() {
12
73
< div class = 'max-w-md w-full space-y-8' >
13
74
< div >
14
75
< 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' }
16
77
</ 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
+ ) }
17
88
</ div >
18
- < Decrypt />
89
+ < Decrypt fileExists = { props . data ?. fileExists } />
19
90
</ div >
20
91
</ div >
21
92
</ >
0 commit comments