@@ -19,6 +19,7 @@ import {
1919 S3Client ,
2020} from '@aws-sdk/client-s3'
2121import { Upload as S3Upload } from '@aws-sdk/lib-storage'
22+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
2223import { Storage as GcsClient } from '@google-cloud/storage'
2324import { chunk } from 'remeda'
2425import { match } from 'ts-pattern'
@@ -262,7 +263,7 @@ class Storage {
262263 return { id : uploadId }
263264 }
264265
265- async getCacheEntry ( {
266+ private async getCacheEntryByKeys ( {
266267 keys : [ primaryKey , ...restoreKeys ] ,
267268 version,
268269 } : {
@@ -310,6 +311,35 @@ class Storage {
310311 if ( prefixedMatch ) return prefixedMatch
311312 }
312313 }
314+
315+ async getCacheEntryWithDownloadUrl ( args : Parameters < typeof this . getCacheEntryByKeys > [ 0 ] ) {
316+ const cacheEntry = await this . getCacheEntryByKeys ( args )
317+ if ( ! cacheEntry ) return
318+
319+ const defaultUrl = `${ env . API_BASE_URL } /download/${ cacheEntry . id } `
320+
321+ if ( ! env . ENABLE_DIRECT_DOWNLOADS || ! this . adapter . createDownloadUrl )
322+ return {
323+ downloadUrl : defaultUrl ,
324+ cacheEntry,
325+ }
326+
327+ const location = await this . db
328+ . selectFrom ( 'storage_locations' )
329+ . where ( 'id' , '=' , cacheEntry . locationId )
330+ . select ( [ 'folderName' , 'mergedAt' ] )
331+ . executeTakeFirst ( )
332+ if ( ! location ) throw new Error ( 'Storage location not found' )
333+
334+ const downloadUrl = location . mergedAt
335+ ? await this . adapter . createDownloadUrl ( `${ location . folderName } /merged` )
336+ : defaultUrl
337+
338+ return {
339+ downloadUrl,
340+ cacheEntry,
341+ }
342+ }
313343}
314344
315345export const getStorage = createSingletonPromise ( async ( ) => Storage . fromEnv ( ) )
@@ -319,6 +349,7 @@ interface StorageAdapter {
319349 uploadStream ( objectName : string , stream : ReadableStream ) : Promise < void >
320350 deleteFolder ( folderName : string ) : Promise < void >
321351 countFilesInFolder ( folderName : string ) : Promise < number >
352+ createDownloadUrl ?( objectName : string ) : Promise < string >
322353}
323354
324355class S3Adapter implements StorageAdapter {
@@ -417,6 +448,19 @@ class S3Adapter implements StorageAdapter {
417448
418449 return listResponse . KeyCount ?? 0
419450 }
451+
452+ async createDownloadUrl ( objectName : string ) {
453+ return getSignedUrl (
454+ this . s3 ,
455+ new GetObjectCommand ( {
456+ Bucket : this . bucket ,
457+ Key : `${ this . keyPrefix } /${ objectName } ` ,
458+ } ) ,
459+ {
460+ expiresIn : 10 * 60 * 1000 , // 10min
461+ } ,
462+ )
463+ }
420464}
421465
422466class FileSystemAdapter implements StorageAdapter {
@@ -513,4 +557,14 @@ class GcsAdapter implements StorageAdapter {
513557 } )
514558 . then ( ( res ) => res [ 0 ] . length )
515559 }
560+
561+ async createDownloadUrl ( objectName : string ) {
562+ return this . bucket
563+ . file ( `${ this . keyPrefix } /${ objectName } ` )
564+ . getSignedUrl ( {
565+ action : 'read' ,
566+ expires : Date . now ( ) + 10 * 60 * 1000 , // 10min
567+ } )
568+ . then ( ( res ) => res [ 0 ] )
569+ }
516570}
0 commit comments