@@ -2,7 +2,16 @@ import { api } from "encore.dev/api";
2
2
import log from "encore.dev/log" ;
3
3
import busboy from "busboy" ;
4
4
import { SQLDatabase } from "encore.dev/storage/sqldb" ;
5
- import { APICallMeta , appMeta , currentRequest } from "encore.dev" ;
5
+ import { APICallMeta , appMeta , currentRequest } from "encore.dev" ; // Define a bucket named 'profile-files' for storing files.
6
+ import { Bucket } from "encore.dev/storage/objects" ;
7
+
8
+ // Define a bucket named 'profile-files' for storing files.
9
+ // Making it possible to get public URLs to files in the bucket
10
+ // by setting 'public' to true
11
+ export const filesBucket = new Bucket ( "profile-files" , {
12
+ versioned : false ,
13
+ public : true ,
14
+ } ) ;
6
15
7
16
// Define a database named 'files', using the database migrations
8
17
// in the "./migrations" folder. Encore automatically provisions,
@@ -11,7 +20,7 @@ export const DB = new SQLDatabase("files", {
11
20
migrations : "./migrations" ,
12
21
} ) ;
13
22
14
- type FileEntry = { data : any [ ] ; filename : string } ;
23
+ type FileEntry = { data : any [ ] ; filename : string ; mimeType : string } ;
15
24
16
25
/**
17
26
* Raw endpoint for storing a single file to the database.
@@ -24,9 +33,10 @@ export const save = api.raw(
24
33
headers : req . headers ,
25
34
limits : { files : 1 } ,
26
35
} ) ;
27
- const entry : FileEntry = { filename : "" , data : [ ] } ;
36
+ const entry : FileEntry = { filename : "" , data : [ ] , mimeType : "" } ;
28
37
29
38
bb . on ( "file" , ( _ , file , info ) => {
39
+ entry . mimeType = info . mimeType ;
30
40
entry . filename = info . filename ;
31
41
file
32
42
. on ( "data" , ( data ) => {
@@ -43,11 +53,19 @@ export const save = api.raw(
43
53
bb . on ( "close" , async ( ) => {
44
54
try {
45
55
const buf = Buffer . concat ( entry . data ) ;
56
+
57
+ // Save file to bucket
58
+ await filesBucket . upload ( entry . filename , buf , {
59
+ contentType : entry . mimeType ,
60
+ } ) ;
61
+
62
+ // Save file to DB
46
63
await DB . exec `
47
- INSERT INTO files (name, data)
48
- VALUES (${ entry . filename } , ${ buf } )
64
+ INSERT INTO files (name, data, mime_type )
65
+ VALUES (${ entry . filename } , ${ buf } , ${ entry . mimeType } )
49
66
ON CONFLICT (name) DO UPDATE
50
- SET data = ${ buf }
67
+ SET data = ${ buf } ,
68
+ mime_type = ${ entry . mimeType }
51
69
` ;
52
70
log . info ( `File ${ entry . filename } saved` ) ;
53
71
@@ -80,7 +98,7 @@ export const saveMultiple = api.raw(
80
98
const entries : FileEntry [ ] = [ ] ;
81
99
82
100
bb . on ( "file" , ( _ , file , info ) => {
83
- const entry : FileEntry = { filename : info . filename , data : [ ] } ;
101
+ const entry : FileEntry = { filename : "" , data : [ ] , mimeType : "" } ;
84
102
85
103
file
86
104
. on ( "data" , ( data ) => {
@@ -98,11 +116,19 @@ export const saveMultiple = api.raw(
98
116
try {
99
117
for ( const entry of entries ) {
100
118
const buf = Buffer . concat ( entry . data ) ;
119
+
120
+ // Save file to Bucket
121
+ await filesBucket . upload ( entry . filename , buf , {
122
+ contentType : entry . mimeType ,
123
+ } ) ;
124
+
125
+ // Save file to DB
101
126
await DB . exec `
102
- INSERT INTO files (name, data)
103
- VALUES (${ entry . filename } , ${ buf } )
127
+ INSERT INTO files (name, data, mime_type )
128
+ VALUES (${ entry . filename } , ${ buf } , ${ entry . mimeType } )
104
129
ON CONFLICT (name) DO UPDATE
105
- SET data = ${ buf }
130
+ SET data = ${ buf } ,
131
+ mime_type = ${ entry . mimeType }
106
132
` ;
107
133
log . info ( `File ${ entry . filename } saved` ) ;
108
134
}
@@ -125,16 +151,6 @@ export const saveMultiple = api.raw(
125
151
} ,
126
152
) ;
127
153
128
- // Helper function for saving a file to the database
129
- const saveToDb = async ( name : string , data : Buffer ) => {
130
- return await DB . exec `
131
- INSERT INTO files (name, data)
132
- VALUES (${ name } , ${ data } )
133
- ON CONFLICT (name) DO UPDATE
134
- SET data = ${ data }
135
- ` ;
136
- } ;
137
-
138
154
// Raw endpoint for serving a file from the database
139
155
export const get = api . raw (
140
156
{ expose : true , method : "GET" , path : "/files/:name" } ,
@@ -151,6 +167,7 @@ export const get = api.raw(
151
167
return ;
152
168
}
153
169
170
+ resp . writeHead ( 200 , { "Content-Type" : row . mime_type } ) ;
154
171
const chunk = Buffer . from ( row . data ) ;
155
172
resp . writeHead ( 200 , { Connection : "close" } ) ;
156
173
resp . end ( chunk ) ;
@@ -166,8 +183,8 @@ interface ListResponse {
166
183
}
167
184
168
185
// API endpoint for listing all files in the database
169
- export const list = api (
170
- { expose : true , method : "GET" , path : "/files" } ,
186
+ export const listDBFiles = api (
187
+ { expose : true , method : "GET" , path : "/db- files" } ,
171
188
async ( ) : Promise < ListResponse > => {
172
189
const rows = await DB . query `
173
190
SELECT name
@@ -188,6 +205,23 @@ export const list = api(
188
205
} ,
189
206
) ;
190
207
208
+ // API endpoint for listing all files in the bucket
209
+ export const listBucketFiles = api (
210
+ { expose : true , method : "GET" , path : "/bucket-files" } ,
211
+ async ( ) : Promise < ListResponse > => {
212
+ const resp : ListResponse = { files : [ ] } ;
213
+
214
+ for await ( const entry of filesBucket . list ( { } ) ) {
215
+ resp . files . push ( {
216
+ url : filesBucket . publicUrl ( entry . name ) ,
217
+ name : entry . name ,
218
+ } ) ;
219
+ }
220
+
221
+ return resp ;
222
+ } ,
223
+ ) ;
224
+
191
225
// Serving some static HTML for demo purposes
192
226
export const frontend = api . raw (
193
227
{ expose : true , path : "/!path" , method : "GET" } ,
@@ -209,12 +243,17 @@ export const frontend = api.raw(
209
243
<input type="submit">
210
244
</form>
211
245
<br/>
212
- <h2>Files:</h2>
246
+ <h2>Files in DB:</h2>
247
+ <div id="bd-files"></div>
248
+
249
+ <h2>Files in Bucket:</h2>
250
+ <div id="bucket-files"></div>
213
251
214
252
<script>
215
- async function getData() {
253
+ async function getData(elementId, url) {
254
+ const el = document.getElementById(elementId);
216
255
try {
217
- const response = await fetch("/files" );
256
+ const response = await fetch(url );
218
257
const json = await response.json();
219
258
const list = document.createElement("ul");
220
259
json.files.forEach((file) => {
@@ -225,12 +264,13 @@ export const frontend = api.raw(
225
264
item.appendChild(link);
226
265
list.appendChild(item);
227
266
});
228
- document.body .appendChild(list);
267
+ el .appendChild(list);
229
268
} catch (error) {
230
269
console.error(error.message);
231
270
}
232
271
}
233
- getData();
272
+ getData("bd-files", "/db-files");
273
+ getData("bucket-files", "/bucket-files");
234
274
</script>
235
275
</body>
236
276
</html>
0 commit comments