@@ -3,55 +3,65 @@ import {
3
3
Get ,
4
4
Headers ,
5
5
Put ,
6
- Query ,
7
6
Request ,
8
7
Response ,
9
8
} from '@nestjs/common' ;
10
9
import { Request as IRequest , Response as IResponse } from 'express' ;
11
10
import { DateTime } from 'luxon' ;
11
+ import { URL } from 'node:url' ;
12
12
import rawBody from 'raw-body' ;
13
- import { InputException , ServerException } from '../.. /common' ;
14
- import { FileBucket , LocalBucket } from './bucket' ;
13
+ import { InputException } from '~ /common' ;
14
+ import { FileBucket , InvalidSignedUrlException } from './bucket' ;
15
15
16
+ /**
17
+ * This fakes S3 web hosting for use with LocalBuckets.
18
+ */
16
19
@Controller ( LocalBucketController . path )
17
20
export class LocalBucketController {
18
21
static path = '/local-bucket' ;
19
22
20
- private readonly bucket : LocalBucket | undefined ;
21
- constructor ( bucket : FileBucket ) {
22
- this . bucket = bucket instanceof LocalBucket ? bucket : undefined ;
23
- }
23
+ constructor ( private readonly bucket : FileBucket ) { }
24
24
25
25
@Put ( )
26
26
async upload (
27
27
@Headers ( 'content-type' ) contentType : string ,
28
- @Query ( 'signed' ) signed : string ,
29
28
@Request ( ) req : IRequest ,
30
29
) {
31
- if ( ! this . bucket ) {
32
- throw new ServerException ( 'Cannot upload file here' ) ;
33
- }
34
30
// Chokes on json files because they are parsed with body-parser.
35
31
// Need to disable it for this path or create a workaround.
36
32
const contents = await rawBody ( req ) ;
37
33
if ( ! contents ) {
38
34
throw new InputException ( ) ;
39
35
}
40
36
41
- await this . bucket . upload ( signed , {
37
+ const url = new URL ( `https://localhost${ req . url } ` ) ;
38
+ const parsed = await this . bucket . parseSignedUrl ( url ) ;
39
+ if ( parsed . operation !== 'PutObject' ) {
40
+ throw new InvalidSignedUrlException ( ) ;
41
+ }
42
+ await this . bucket . putObject ( {
43
+ Key : parsed . Key ,
42
44
Body : contents ,
43
45
ContentType : contentType ,
44
46
} ) ;
47
+
45
48
return { ok : true } ;
46
49
}
47
50
48
51
@Get ( )
49
- async download ( @Query ( 'signed' ) signed : string , @Response ( ) res : IResponse ) {
50
- if ( ! this . bucket ) {
51
- throw new ServerException ( 'Cannot download file here' ) ;
52
+ async download ( @Request ( ) req : IRequest , @Response ( ) res : IResponse ) {
53
+ const url = new URL ( `https://localhost${ req . url } ` ) ;
54
+ const { Key, operation, ...rest } = await this . bucket . parseSignedUrl ( url ) ;
55
+ if ( operation !== 'GetObject' ) {
56
+ throw new InvalidSignedUrlException ( ) ;
52
57
}
53
-
54
- const out = await this . bucket . download ( signed ) ;
58
+ const signedParams = Object . fromEntries (
59
+ Object . entries ( rest ) . flatMap ( ( [ k , v ] ) =>
60
+ v != null ? [ [ k . replace ( / ^ R e s p o n s e / , '' ) , v ] ] : [ ] ,
61
+ ) ,
62
+ ) ;
63
+ const bucketObject = await this . bucket . getObject ( Key ) ;
64
+ const out = { ...bucketObject , ...signedParams } ;
55
65
56
66
const headers = {
57
67
'Cache-Control' : out . CacheControl ,
0 commit comments