Skip to content

Commit 3594bfb

Browse files
committed
chore: bucket changes
1 parent 09a580d commit 3594bfb

File tree

7 files changed

+62
-62
lines changed

7 files changed

+62
-62
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import { useValidatedParams, useValidatedQuery, z } from 'h3-zod'
1+
import { useValidatedParams, z } from 'h3-zod'
22

33
export default eventHandler(async (event) => {
44
// TODO: handle authorization
55

6-
const { name } = await useValidatedQuery(event, {
7-
name: z.ostring()
8-
})
96
const { key } = await useValidatedParams(event, {
107
key: z.string().min(1)
118
})
129

13-
return useBlob(name).delete(key)
10+
return useBlob().delete(key)
1411
})
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import { useValidatedParams, useValidatedQuery, z } from 'h3-zod'
1+
import { useValidatedParams, z } from 'h3-zod'
22

33
export default eventHandler(async (event) => {
44
// TODO: handle authorization
55

6-
const { name } = await useValidatedQuery(event, {
7-
name: z.ostring()
8-
})
96
const { key } = await useValidatedParams(event, {
107
key: z.string().min(1)
118
})
129

13-
return useBlob(name).get(key)
10+
return useBlob().get(key)
1411
})
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
import { useValidatedQuery, z } from 'h3-zod'
2-
31
export default eventHandler(async (event) => {
42
// TODO: handle authorization
53

6-
const { name } = await useValidatedQuery(event, {
7-
name: z.ostring()
8-
})
9-
10-
return useBlob(name).list()
4+
return useBlob().list()
115
})

_nuxthub/server/utils/bucket.ts

+45-31
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import type { R2Bucket, R2ListOptions } from '@cloudflare/workers-types/experimental'
2-
import type { MultiPartData } from 'h3'
2+
import type { EventHandlerRequest, H3Event } from 'h3'
33
import mime from 'mime'
44
import { imageMeta } from 'image-meta'
55
import { defu } from 'defu'
66
import { randomUUID } from 'uncrypto'
7+
import { parse } from 'pathe'
8+
import { joinURL } from 'ufo'
79

810
const _buckets: Record<string, R2Bucket> = {}
911

10-
export function useBucket (name: string = '') {
11-
const bucketName = name ? `BUCKET_${name.toUpperCase()}` : 'BUCKET'
12+
function useBucket () {
13+
const bucketName = 'BUCKET'
1214
if (_buckets[bucketName]) {
1315
return _buckets[bucketName]
1416
}
@@ -26,18 +28,17 @@ export function useBucket (name: string = '') {
2628
return _buckets[bucketName]
2729
}
2830

29-
export function useBlob (name: string = '') {
31+
export function useBlob () {
3032
const proxy = import.meta.dev && process.env.NUXT_HUB_URL
3133

3234
return {
3335
async list (options: R2ListOptions = {}) {
3436
if (proxy) {
3537
const query: Record<string, any> = {}
36-
if (name) { query.name = name }
3738

3839
return $fetch<R2Object[]>('/api/_hub/bucket', { baseURL: proxy, method: 'GET', query })
3940
} else {
40-
const bucket = useBucket(name)
41+
const bucket = useBucket()
4142

4243
const resolvedOptions = defu(options, {
4344
limit: 500,
@@ -60,55 +61,55 @@ export function useBlob (name: string = '') {
6061
cursor = next.truncated ? next.cursor : undefined
6162
}
6263

63-
return listed.objects
64+
return listed.objects.map(mapR2ObjectToBlob)
6465
}
6566
},
6667
async get (key: string) {
6768
if (proxy) {
6869
const query: Record<string, any> = {}
69-
if (name) { query.name = name }
7070

7171
return $fetch<ReadableStreamDefaultReader<any>>(`/api/_hub/bucket/${key}`, { baseURL: proxy, method: 'GET', query })
7272
} else {
73-
const bucket = useBucket(name)
73+
const bucket = useBucket()
7474
const object = await bucket.get(key)
7575

7676
if (!object) {
7777
throw createError({ message: 'File not found', statusCode: 404 })
7878
}
7979

80-
// setHeader(useEvent(), 'Content-Type', object.httpMetadata!.contentType!)
81-
// setHeader(useEvent(), 'Content-Length', object.size)
80+
// FIXME
81+
setHeader(useEvent(), 'Content-Type', object.httpMetadata!.contentType!)
82+
setHeader(useEvent(), 'Content-Length', object.size)
8283

8384
return object.body.getReader()
8485
}
8586
},
86-
async put (file: MultiPartData) {
87+
async put (pathname: string, body: string | ReadableStream<any> | ArrayBuffer | ArrayBufferView | Blob, options: { contentType?: string, addRandomSuffix?: boolean, [key: string]: any } = { addRandomSuffix: true }) {
8788
if (proxy) {
8889
// TODO
8990
} else {
90-
const bucket = useBucket(name)
91-
92-
const type = file.type || getContentType(file.filename)
93-
// TODO: ensure key unicity
94-
const key = randomUUID()
95-
const httpMetadata = { contentType: type }
96-
const customMetadata: Record<string, any> = {
97-
...getMetadata(type, file.data),
98-
filename: file.filename
91+
const bucket = useBucket()
92+
const fileContentType = (body as Blob).type || getContentType(pathname)
93+
const { contentType, addRandomSuffix, ...customMetadata } = options
94+
95+
const { dir, ext, name: filename } = parse(pathname)
96+
let key = pathname
97+
if (addRandomSuffix) {
98+
key = joinURL(dir === '.' ? '' : dir, `${filename}-${randomUUID().split('-')[0]}${ext}`)
9999
}
100100

101-
return await bucket.put(key, toArrayBuffer(file.data), { httpMetadata, customMetadata })
101+
const object = await bucket.put(key, body as any, { httpMetadata: { contentType: contentType || fileContentType }, customMetadata })
102+
103+
return mapR2ObjectToBlob(object)
102104
}
103105
},
104106
async delete (key: string) {
105107
if (proxy) {
106108
const query: Record<string, any> = {}
107-
if (name) { query.name = name }
108109

109110
return $fetch<void>(`/api/_hub/bucket/${key}`, { baseURL: proxy, method: 'DELETE', query })
110111
} else {
111-
const bucket = useBucket(name)
112+
const bucket = useBucket()
112113

113114
return await bucket.delete(key)
114115
}
@@ -131,15 +132,19 @@ function getContentType (pathOrExtension?: string) {
131132
return (pathOrExtension && mime.getType(pathOrExtension)) || 'application/octet-stream'
132133
}
133134

134-
function getMetadata (type: string, buffer: Buffer) {
135-
if (type.startsWith('image/')) {
136-
return imageMeta(buffer) as Record<string, any>
137-
} else {
138-
return {}
135+
export function getMetadata (filename: string, buffer: Buffer) {
136+
const metadata: Record<string, any> = {
137+
contentType: getContentType(filename)
138+
}
139+
140+
if (metadata.contentType.startsWith('image/')) {
141+
Object.assign(metadata, imageMeta(buffer))
139142
}
143+
144+
return metadata
140145
}
141146

142-
function toArrayBuffer (buffer: Buffer) {
147+
export function toArrayBuffer (buffer: Buffer) {
143148
const arrayBuffer = new ArrayBuffer(buffer.length)
144149
const view = new Uint8Array(arrayBuffer)
145150
for (let i = 0; i < buffer.length; ++i) {
@@ -153,4 +158,13 @@ export async function readFiles (event: H3Event<EventHandlerRequest>) {
153158

154159
// Filter only files
155160
return files.filter((file) => Boolean(file.filename))
156-
}
161+
}
162+
163+
function mapR2ObjectToBlob (object: R2Object) {
164+
return {
165+
pathname: object.key,
166+
contentType: object.httpMetadata?.contentType,
167+
size: object.size,
168+
uploadedAt: object.uploaded,
169+
}
170+
}

_nuxthub/server/utils/kv.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Storage } from 'unstorage'
2-
import { createStorage, prefixStorage } from 'unstorage'
2+
import { createStorage } from 'unstorage'
33
import fsDriver from 'unstorage/drivers/fs'
44
import httpDriver from 'unstorage/drivers/http'
55
import cloudflareKVBindingDriver from 'unstorage/drivers/cloudflare-kv-binding'

pages/storage.vue

+10-10
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ function onFileSelect (e: any) {
4545
target.value = ''
4646
}
4747
48-
async function deleteFile (key: string) {
48+
async function deleteFile (pathname: string) {
4949
try {
50-
await useFetch(`/api/storage/${key}`, { method: 'DELETE' })
51-
storage.value = storage.value!.filter(t => t.key !== key)
52-
toast.add({ title: `File "${key}" deleted.` })
50+
await useFetch(`/api/storage/${pathname}`, { method: 'DELETE' })
51+
storage.value = storage.value!.filter(t => t.pathname !== pathname)
52+
toast.add({ title: `File "${pathname}" deleted.` })
5353
} catch (err: any) {
5454
const title = err.data?.data?.issues?.map((issue: any) => issue.message).join('\n') || err.message()
5555
toast.add({ title, color: 'red' })
@@ -111,7 +111,7 @@ const items = [[{
111111
<div class="grid grid-cols-1 md:grid-cols-3 gap-2">
112112
<UCard
113113
v-for="file of storage"
114-
:key="file.key"
114+
:key="file.pathname"
115115
:ui="{
116116
body: {
117117
base: 'space-y-0',
@@ -125,18 +125,18 @@ const items = [[{
125125
<UIcon name="i-heroicons-document" class="w-8 h-8" />
126126
</div>
127127
<div class="flex flex-col gap-1 p-2 border-t border-gray-200 dark:border-gray-800">
128-
<span class="text-sm font-medium">{{ file.key }}</span>
128+
<span class="text-sm font-medium">{{ file.pathname }}</span>
129129
<div class="flex items-center justify-between gap-1">
130-
<span class="text-xs truncate">{{ file.httpMetadata?.contentType || '-' }}</span>
130+
<span class="text-xs truncate">{{ file.contentType || '-' }}</span>
131131
<span class="text-xs">{{ file.size ? `${Math.round(file.size / Math.pow(1024, 2) * 100) / 100}MB` : '-' }}</span>
132132
</div>
133-
<div v-for="[key, value] of Object.entries(file.customMetadata || {})" :key="key" class="flex items-center justify-between gap-1">
133+
<!-- <div v-for="[key, value] of Object.entries(file.customMetadata || {})" :key="key" class="flex items-center justify-between gap-1">
134134
<span class="text-xs">{{ key }}</span>
135135
<span class="text-xs truncate">{{ value }}</span>
136-
</div>
136+
</div> -->
137137
</div>
138138

139-
<UButton icon="i-heroicons-x-mark" variant="link" color="primary" class="absolute top-0 right-0" @click="deleteFile(file.key)" />
139+
<UButton icon="i-heroicons-x-mark" variant="link" color="primary" class="absolute top-0 right-0" @click="deleteFile(file.pathname)" />
140140
</UCard>
141141
</div>
142142
</UCard>

server/api/storage/index.put.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ export default eventHandler(async (event) => {
77
}
88

99
const { put } = useBlob()
10-
1110
const objects = []
12-
1311
try {
1412
for (const file of files) {
15-
const object = await put(file)
13+
const object = await put(file.filename!, toArrayBuffer(file.data), { addRandomSuffix: true, ...getMetadata(file.filename!, file.data) })
1614
objects.push(object)
1715
}
1816
} catch (e: any) {

0 commit comments

Comments
 (0)