Skip to content

Commit e40c1be

Browse files
authored
feat: custom file size limit and mime types at bucket level (#151)
1 parent c695143 commit e40c1be

File tree

6 files changed

+114
-6
lines changed

6 files changed

+114
-6
lines changed

infra/storage/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
FROM supabase/storage-api:v0.28.0
1+
FROM supabase/storage-api:v0.29.1
22

33
RUN apk add curl --no-cache

src/lib/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export interface Bucket {
22
id: string
33
name: string
44
owner: string
5+
file_size_limit?: number
6+
allowed_mime_types?: string[]
57
created_at: string
68
updated_at: string
79
public: boolean

src/packages/StorageBucketApi.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,15 @@ export default class StorageBucketApi {
7474
*
7575
* @param id A unique identifier for the bucket you are creating.
7676
* @param options.public The visibility of the bucket. Public buckets don't require an authorization token to download objects, but still require a valid token for all other operations. By default, buckets are private.
77+
* @param options.fileSizeLimit specifies the file size limit that this bucket can accept during upload
78+
* @param options.allowedMimeTypes specifies the allowed mime types that this bucket can accept during upload
7779
* @returns newly created bucket id
7880
*/
7981
async createBucket(
8082
id: string,
81-
options: { public: boolean } = { public: false }
83+
options: { public: boolean; fileSizeLimit?: number | string; allowedMimeTypes?: string[] } = {
84+
public: false,
85+
}
8286
): Promise<
8387
| {
8488
data: Pick<Bucket, 'name'>
@@ -93,7 +97,13 @@ export default class StorageBucketApi {
9397
const data = await post(
9498
this.fetch,
9599
`${this.url}/bucket`,
96-
{ id, name: id, public: options.public },
100+
{
101+
id,
102+
name: id,
103+
public: options.public,
104+
file_size_limit: options.fileSizeLimit,
105+
allowed_mime_types: options.allowedMimeTypes,
106+
},
97107
{ headers: this.headers }
98108
)
99109
return { data, error: null }
@@ -111,10 +121,12 @@ export default class StorageBucketApi {
111121
*
112122
* @param id A unique identifier for the bucket you are updating.
113123
* @param options.public The visibility of the bucket. Public buckets don't require an authorization token to download objects, but still require a valid token for all other operations.
124+
* @param options.fileSizeLimit specifies the file size limit that this bucket can accept during upload
125+
* @param options.allowedMimeTypes specifies the allowed mime types that this bucket can accept during upload
114126
*/
115127
async updateBucket(
116128
id: string,
117-
options: { public: boolean }
129+
options: { public: boolean; fileSizeLimit?: number | string; allowedMimeTypes?: string[] }
118130
): Promise<
119131
| {
120132
data: { message: string }
@@ -129,7 +141,13 @@ export default class StorageBucketApi {
129141
const data = await put(
130142
this.fetch,
131143
`${this.url}/bucket/${id}`,
132-
{ id, name: id, public: options.public },
144+
{
145+
id,
146+
name: id,
147+
public: options.public,
148+
file_size_limit: options.fileSizeLimit,
149+
allowed_mime_types: options.allowedMimeTypes,
150+
},
133151
{ headers: this.headers }
134152
)
135153
return { data, error: null }

test/__snapshots__/storageApi.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
exports[`bucket api Get bucket by id 1`] = `
44
Object {
5+
"allowed_mime_types": null,
56
"created_at": "2021-02-17T04:43:32.770206+00:00",
7+
"file_size_limit": 0,
68
"id": "bucket2",
79
"name": "bucket2",
810
"owner": "4d56e902-f0a0-4662-8448-a4d9e643c142",
@@ -25,6 +27,12 @@ Object {
2527
}
2628
`;
2729

30+
exports[`bucket api partially update bucket 1`] = `
31+
Object {
32+
"message": "Successfully updated",
33+
}
34+
`;
35+
2836
exports[`bucket api update bucket 1`] = `
2937
Object {
3038
"message": "Successfully updated",

test/storageApi.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,35 @@ describe('bucket api', () => {
4242
})
4343

4444
test('update bucket', async () => {
45-
const updateRes = await storage.updateBucket(newBucketName, { public: true })
45+
const newBucketName = `my-new-bucket-${Date.now()}`
46+
await storage.createBucket(newBucketName)
47+
const updateRes = await storage.updateBucket(newBucketName, {
48+
public: true,
49+
fileSizeLimit: '20mb',
50+
allowedMimeTypes: ['image/jpeg'],
51+
})
4652
expect(updateRes.error).toBeNull()
4753
expect(updateRes.data).toMatchSnapshot()
4854
const getRes = await storage.getBucket(newBucketName)
4955
expect(getRes.data!.public).toBe(true)
56+
expect(getRes.data!.file_size_limit).toBe(20000000)
57+
expect(getRes.data!.allowed_mime_types).toEqual(['image/jpeg'])
58+
})
59+
60+
test('partially update bucket', async () => {
61+
const newBucketName = `my-new-bucket-${Date.now()}`
62+
await storage.createBucket(newBucketName, {
63+
public: true,
64+
fileSizeLimit: '20mb',
65+
allowedMimeTypes: ['image/jpeg'],
66+
})
67+
const updateRes = await storage.updateBucket(newBucketName, { public: false })
68+
expect(updateRes.error).toBeNull()
69+
expect(updateRes.data).toMatchSnapshot()
70+
const getRes = await storage.getBucket(newBucketName)
71+
expect(getRes.data!.public).toBe(false)
72+
expect(getRes.data!.file_size_limit).toBe(20000000)
73+
expect(getRes.data!.allowed_mime_types).toEqual(['image/jpeg'])
5074
})
5175

5276
test('empty bucket', async () => {

test/storageFileApi.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,62 @@ describe('Object API', () => {
159159
expect(updateRes.error).toBeNull()
160160
expect(updateRes.data?.path).toEqual(uploadPath)
161161
})
162+
163+
test('can upload a file within the file size limit', async () => {
164+
const bucketName = 'with-limit' + Date.now()
165+
await storage.createBucket(bucketName, {
166+
public: true,
167+
fileSizeLimit: '1mb',
168+
})
169+
170+
const res = await storage.from(bucketName).upload(uploadPath, file)
171+
expect(res.error).toBeNull()
172+
})
173+
174+
test('cannot upload a file that exceed the file size limit', async () => {
175+
const bucketName = 'with-limit' + Date.now()
176+
await storage.createBucket(bucketName, {
177+
public: true,
178+
fileSizeLimit: '1kb',
179+
})
180+
181+
const res = await storage.from(bucketName).upload(uploadPath, file)
182+
expect(res.error).toEqual({
183+
error: 'Payload too large',
184+
message: 'The object exceeded the maximum allowed size',
185+
statusCode: '413',
186+
})
187+
})
188+
189+
test('can upload a file with a valid mime type', async () => {
190+
const bucketName = 'with-limit' + Date.now()
191+
await storage.createBucket(bucketName, {
192+
public: true,
193+
allowedMimeTypes: ['image/png'],
194+
})
195+
196+
const res = await storage.from(bucketName).upload(uploadPath, file, {
197+
contentType: 'image/png',
198+
})
199+
expect(res.error).toBeNull()
200+
})
201+
202+
test('cannot upload a file an invalid mime type', async () => {
203+
const bucketName = 'with-limit' + Date.now()
204+
await storage.createBucket(bucketName, {
205+
public: true,
206+
allowedMimeTypes: ['image/png'],
207+
})
208+
209+
const res = await storage.from(bucketName).upload(uploadPath, file, {
210+
contentType: 'image/jpeg',
211+
})
212+
expect(res.error).toEqual({
213+
error: 'invalid_mime_type',
214+
message: 'mime type not supported',
215+
statusCode: '422',
216+
})
217+
})
162218
})
163219

164220
describe('File operations', () => {

0 commit comments

Comments
 (0)